/* radare - LGPL - Copyright 2024-2026 - pancake */

#include <r_util.h>
#include <r_anal.h>

typedef struct {
	RStrs keys[10];
	RStrs values[10];
	size_t count;
} AttrList;

typedef struct {
	char *name;
	char *type;
} TypedefEntry;

typedef struct {
	RStrBuf *sb;
	int line;
	AttrList attrs;
	RStrs s;
	const char *error;
	char *error_msg;
	TypedefEntry tdefs[64];
	size_t tdef_count;
	int struct_pack;
	int ptr_size;
} KVCParser;

typedef bool(*KVCParserCallback)(KVCParser *, const char *);

// serialized later with r_anal_base_type_to_kv() so parser and importers share one sdb schema
static void kvc_basetype_add_member(RAnalBaseType *bt, const char *name, const char *type, int offset, int count) {
	RAnalTypeMember *m = RVecAnalTypeMember_emplace_back (r_anal_base_type_members (bt));
	if (m) {
		m->name = strdup (name);
		m->type = strdup (type);
		m->offset = offset;
		m->count = (count > 0)? (size_t)count: 0;
	}
}

static void kvc_basetype_emit(KVCParser *kvc, RAnalBaseType *bt) {
	char *kv = r_anal_base_type_to_kv (bt);
	if (kv) {
		r_strbuf_append (kvc->sb, kv);
		free (kv);
	}
	r_anal_base_type_free (bt);
}

static char *collapse_whitespace(RStrs s) {
	r_strs_trim (&s);
	char *out = r_strs_tostring (s);
	if (!out) {
		return NULL;
	}
	char *d = out;
	bool in_space = false;
	for (const char *src = out; *src; src++) {
		if (isspace ((unsigned char)*src)) {
			if (!in_space) {
				*d++ = ' ';
				in_space = true;
			}
		} else {
			*d++ = *src;
			in_space = false;
		}
	}
	*d = 0;
	return out;
}

#include "pp.inc.c"

static const char kvc_getch(KVCParser *kvc) {
	if (!r_strs_empty (kvc->s)) {
		const char ch = *(kvc->s.a);
		if (ch == '\n') {
			kvc->line++;
		}
		kvc->s.a++;
		return ch;
	}
	return 0; // EOF
}

static const char kvc_peek(KVCParser *kvc, int delta) {
	return (delta >= 0)? r_strs_at (kvc->s, (size_t)delta): 0;
}

static void kvc_error(KVCParser *kvc, const char *msg) {
	free (kvc->error_msg);
	kvc->error_msg = r_str_newf ("at line %d: %s", kvc->line, msg);
	kvc->error = msg;
	kvc->s.a = kvc->s.b;
}

static void massage_type(char **s) {
	// Skip leading semicolons
	char *str = *s;
	if (!str) {
		return;
	}
	while (*str == ';') {
		str++;
	}
	// Skip whitespace after semicolons
	while (isspace (*str)) {
		str++;
	}

	if (str != *s) {
		char *new_str = strdup (str);
		free (*s);
		*s = new_str;
	}

	// Handle asterisks in type
	char *star = strchr (*s, '*');
	if (star) {
		char *ostar = star;
		while (star > *s) {
			if (!isspace (*star)) {
				break;
			}
			star--;
		}
		RStrs type_tok = { *s, star };
		r_strs_trim (&type_tok);
		char *type = r_strs_tostring (type_tok);
		char *res = r_str_newf ("%s %s", type, ostar);
		free (*s);
		free (type);
		*s = res;
	}
}

static const char *kvc_peekn(KVCParser *kvc, size_t amount) {
	return (r_strs_len (kvc->s) >= amount)? kvc->s.a: NULL;
}

static const char *kvc_find(KVCParser *kvc, const char *needle) {
	return r_strs_find_strs (kvc->s, r_strs_from (needle));
}

static inline void kvc_skipn(KVCParser *kvc, size_t amount) {
	if (amount <= r_strs_len (kvc->s)) {
		size_t i;
		for (i = 0; i < amount; i++) {
			if (kvc->s.a[i] == '\n') {
				kvc->line++;
			}
		}
		kvc->s.a += amount;
	} else {
		// should not reach this, implies a bug somewhere else
		kvc->s.a = kvc->s.b;
	}
}

static const char *scan_to_semicolon(KVCParser *kvc, bool allow_parens) {
	while (!r_strs_empty (kvc->s)) {
		const char c = kvc_peek (kvc, 0);
		if (c == ';') {
			return kvc->s.a;
		}
		if (!isalnum (c) && !isspace (c) && c != '_' && c != '[' && c != ']' && c != '*') {
			if (!allow_parens || (c != ',' && c != '(' && c != ')' && c != '.')) {
				return NULL;
			}
		}
		kvc_getch (kvc);
	}
	return NULL;
}

static bool skip_until(KVCParser *kvc, char ch) {
	while (!r_strs_empty (kvc->s)) {
		const char c = kvc_peek (kvc, 0);
		if (c == ch) {
			return true;
		}
		kvc_getch (kvc);
	}
	return false;
}

static const char *scan_toplevel(RStrs s, char ch) {
	int depth = 0;
	const char *p;
	for (p = s.a; p < s.b; p++) {
		if (*p == ch && depth == 0) {
			return p;
		}
		if (*p == '(') {
			depth++;
		} else if (*p == ')' && depth > 0) {
			depth--;
		}
	}
	return NULL;
}

static bool capture_paren_group(KVCParser *kvc, RStrs *parm) {
	const char *start = kvc->s.a + 1;
	const char *end = scan_toplevel (r_strs_new (start, kvc->s.b), ')');
	if (!end) {
		return false;
	}
	*parm = r_strs_new (start, end);
	kvc_skipn (kvc, end - kvc->s.a + 1);
	return true;
}

static inline void skip_only_spaces(KVCParser *kvc) {
	RStrs s = kvc->s;
	r_strs_skip_chars (&s, " \t\n\r\v\f");
	kvc_skipn (kvc, s.a - kvc->s.a);
}

static void skip_spaces(KVCParser *kvc) {
	bool again;
	do {
		again = false;
		skip_only_spaces (kvc);
		const char *p = kvc_peekn (kvc, 2);
		if (p && p[0] == '/' && p[1] == '*') {
			kvc_skipn (kvc, 2);
			const char *closing = kvc_find (kvc, "*/");
			if (!closing) {
				kvc_error (kvc, "Unclosed comment");
				return;
			}
			kvc_skipn (kvc, 1 + closing - p);
			again = true;
		}
		p = kvc_peekn (kvc, 3);
		if (p && p[0] == '/' && p[1] == '/' && p[2] != '/') {
			skip_until (kvc, '\n');
			again = true;
		}
	} while (again);
}

static void skip_semicolons(KVCParser *kvc) {
	RStrs s = kvc->s;
	r_strs_skip_chars (&s, " \t\r\v\f;");
	kvc_skipn (kvc, s.a - kvc->s.a);
}

static void skip_ws(KVCParser *kvc) {
	skip_spaces (kvc);
	skip_semicolons (kvc);
	skip_spaces (kvc);
}

static const char *consume_word(KVCParser *kvc) {
	skip_only_spaces (kvc);
	const char *word = kvc->s.a;
	while (true) {
		const char ch = kvc_peek (kvc, 0);
		if (!ch) {
			return false;
		}
		if (!isalnum (ch) && ch != '_' && ch != '-') {
			break;
		}
		if (isspace (ch)) {
			break;
		}
		kvc_getch (kvc);
	}
	return word;
}

static const char *kvc_attr(KVCParser *kvc, const char *k) {
	RStrs s = r_strs_from (k);
	int i;
	for (i = 0; i < kvc->attrs.count; i++) {
		if (r_strs_equals (kvc->attrs.keys[i], s)) {
			return r_strs_tostring (kvc->attrs.values[i]);
		}
	}
	return NULL;
}

static bool parse_attributes(KVCParser *kvc) {
	skip_spaces (kvc);
	const char *begin = kvc_peekn (kvc, 3);
	if (!begin || !r_str_startswith (begin, "///")) {
		return false;
	}
	kvc_skipn (kvc, 3);

	// kvc->attrs.count = 0;
	while (true) {
		int line = kvc->line;
		skip_spaces (kvc);
		if (line != kvc->line) {
			// newline found
			return true;
		}
		char ch = kvc_peek (kvc, 0);
		if (ch != '@') {
			R_LOG_ERROR ("unexpected attribute name must start with @ its '%c'", ch);
			break;
		}
		kvc_getch (kvc);
		const char *name_start = consume_word (kvc);
		if (!name_start) {
			R_LOG_ERROR ("Cannot consume word");
			return false;
		}
		RStrs attr_name = r_strs_new (name_start, kvc->s.a);
		ch = kvc_peek (kvc, 0);
		RStrs attr_value = { 0 };
		if (ch == '(') {
			kvc_getch (kvc);
			const char *val_start = consume_word (kvc);
			if (!val_start) {
				R_LOG_ERROR ("Cannot consume word in value");
				return false;
			}
			ch = kvc_peek (kvc, 0);
			if (ch != ')') {
				R_LOG_ERROR ("Expected )");
				return false;
			}
			attr_value = r_strs_new (val_start, kvc->s.a);
			kvc_getch (kvc);
		} else {
			attr_value = R_STRS_LIT ("true");
		}
		int atidx = kvc->attrs.count;
		bool duppedkey = false;
		{
			int i;
			for (i = 0; i < kvc->attrs.count; i++) {
				if (r_strs_equals (kvc->attrs.keys[i], attr_name)) {
					duppedkey = true;
					atidx = i;
					break;
				}
			}
		}
		if (!duppedkey) {
			if (kvc->attrs.count >= R_ARRAY_SIZE (kvc->attrs.keys)) {
				R_LOG_WARN ("Too many attributes at line %d, ignoring @%.*s", kvc->line, (int)r_strs_len (attr_name), attr_name.a);
				continue;
			}
			kvc->attrs.count++;
			kvc->attrs.keys[atidx] = attr_name;
		}
		kvc->attrs.values[atidx] = attr_value;
	}
	skip_until (kvc, '\n');
	return true;
}

static bool parse_trailing_attributes(KVCParser *kvc) {
	r_strs_skip_chars (&kvc->s, " \t");
	const char *begin = kvc_peekn (kvc, 3);
	if (!begin || !r_str_startswith (begin, "///")) {
		return false;
	}
	return parse_attributes (kvc);
}

static void apply_attributes(KVCParser *kvc, const char *type, const char *scope) {
	int i;
	for (i = 0; i < kvc->attrs.count; i++) {
		RStrs key = kvc->attrs.keys[i];
		RStrs val = kvc->attrs.values[i];
		r_strbuf_appendf (kvc->sb, "%s.%s.@.", type, scope);
		r_strbuf_append_n (kvc->sb, key.a, r_strs_len (key));
		r_strbuf_append (kvc->sb, "=");
		r_strbuf_append_n (kvc->sb, val.a, r_strs_len (val));
		r_strbuf_append (kvc->sb, "\n");
	}
	kvc->attrs.count = 0; // Reset after applying
}

static void token_trim_semi(RStrs *t) {
	r_strs_trim_chars (t, " \t\n\r\v\f;");
}

static void token_typename(RStrs *fun_rtyp, RStrs *fun_name) {
	fun_rtyp->b = fun_name->b;
	token_trim_semi (fun_rtyp);
	token_trim_semi (fun_name);
	if (fun_rtyp->a >= fun_rtyp->b) {
		// empty token after trimming, nothing to split
		fun_name->a = fun_rtyp->a;
		return;
	}
	const bool accept_dots_in_function_names = true;
	const char *p = fun_rtyp->b - 1;
	while (p >= fun_rtyp->a) {
		bool pass = false;
		if (accept_dots_in_function_names) {
			pass = (!isalnum (*p) && *p != '.' && *p != '_') || isspace (*p);
		} else {
			pass = (!isalnum (*p) && *p != '_') || isspace (*p);
		}
		if (pass && *p != '[' && *p != ']') {
			p++;
			break;
		}
		if (p <= fun_rtyp->a) {
			break;
		}
		p--;
	}
	fun_name->a = p;
	fun_rtyp->b = p;
	token_trim_semi (fun_rtyp);
	token_trim_semi (fun_name);
	if (fun_name->a > fun_name->b) {
		fun_name->b = fun_name->a;
	}
	if (fun_rtyp->a > fun_rtyp->b) {
		fun_rtyp->a = fun_rtyp->b;
	}
}

R_IPI int kvc_type_size(const char *name, int dimension, int ptr_size) {
	if (strchr (name, '*')) {
		return ptr_size * dimension;
	}
	if (!strcmp (name, "char") || r_str_endswith (name, "8")) {
		return 1 * dimension;
	}
	if (!strcmp (name, "short") || r_str_endswith (name, "16")) {
		return 2 * dimension;
	}
	if (!strcmp (name, "long long") || !strcmp (name, "double") || r_str_endswith (name, "64")) {
		return 8 * dimension;
	}
	if (r_str_startswith (name, "int") || !strcmp (name, "float") || !strcmp (name, "long")) {
		return 4 * dimension;
	}
	if (dimension > 1) {
		return dimension;
	}
	return 4;
}

R_IPI int kvc_type_align(const char *name, int ptr_size) {
	if (strchr (name, '*')) {
		return ptr_size;
	}
	if (!strcmp (name, "char") || r_str_endswith (name, "8")) {
		return 1;
	}
	if (!strcmp (name, "short") || r_str_endswith (name, "16")) {
		return 2;
	}
	if (!strcmp (name, "long long") || !strcmp (name, "double") || r_str_endswith (name, "64")) {
		return 8;
	}
	return 4;
}

static int kvc_typesize(KVCParser *kvc, const char *name, int dimension) {
	return kvc_type_size (name, dimension, kvc->ptr_size);
}

static int kvc_typealign(KVCParser *kvc, const char *name) {
	return kvc_type_align (name, kvc->ptr_size);
}

static void kvc_align_offset(KVCParser *kvc, int *off, int av) {
	if (kvc->struct_pack > 0 && av > kvc->struct_pack) {
		av = kvc->struct_pack;
	}
	if (av > 1 && (*off % av)) {
		*off += av - (*off % av);
	}
}

static void trim_underscores(RStrs *t) {
	while (t->a[0] == '_') {
		t->a++;
	}
	while (t->b > t->a) {
		t->b--;
		if (t->b[0] != '_') {
			t->b++;
			break;
		}
	}
}

static bool parse_c_attributes(KVCParser *kvc) {
	const char *p = kvc_peekn (kvc, strlen ("__attribute__"));
	if (!p || !r_str_startswith (p, "__attribute__")) {
		return false;
	}
	kvc_skipn (kvc, strlen ("__attribute__"));
	skip_spaces (kvc);
	// Expect double parentheses
	if (kvc_getch (kvc) != '(' || kvc_getch (kvc) != '(') {
		kvc_error (kvc, "Expected __attribute__ ( (...))");
		return false;
	}
	// Parse attribute name
	const char *name_start = kvc->s.a;
	while (isalnum (*kvc->s.a) || *kvc->s.a == '_') {
		kvc_getch (kvc);
	}
	RStrs attr_name = r_strs_new (name_start, kvc->s.a);
	skip_spaces (kvc);
	trim_underscores (&attr_name);
	// Parse optional value
	RStrs attr_value;
	if (kvc_peek (kvc, 0) == '(') {
		kvc_getch (kvc);
		const char *val_start = kvc->s.a;
		const char *close = kvc_find (kvc, ")");
		if (!close) {
			kvc_error (kvc, "Missing ')' in __attribute__");
			return false;
		}
		attr_value = r_strs_new (val_start, close);
		kvc_skipn (kvc, close - val_start + 1);
	} else {
		attr_value = R_STRS_LIT ("true");
	}
	skip_spaces (kvc);
	if (kvc_getch (kvc) != ')') {
		kvc_error (kvc, "Expected ')' after __attribute__");
		return false;
	}
	skip_spaces (kvc);
	// Store attribute
	if (kvc->attrs.count < R_ARRAY_SIZE (kvc->attrs.keys)) {
		int idx = kvc->attrs.count++;
		kvc->attrs.keys[idx] = attr_name;
		kvc->attrs.values[idx] = attr_value;
	} else {
		R_LOG_WARN ("Too many attributes at line %d, ignoring __attribute__((%.*s))", kvc->line, (int)r_strs_len (attr_name), attr_name.a);
	}
	kvc_getch (kvc);
	return true;
}

static void kvc_register_typedef(KVCParser *kvc, const char *name, const char *type) {
	if (kvc->tdef_count < (sizeof (kvc->tdefs) / sizeof (kvc->tdefs[0]))) {
		kvc->tdefs[kvc->tdef_count].name = strdup (name);
		kvc->tdefs[kvc->tdef_count].type = strdup (type);
		kvc->tdef_count++;
	}
}

static const char *kvc_lookup_typedef(KVCParser *kvc, const char *name) {
	int i;
	for (i = 0; i < (int)kvc->tdef_count; i++) {
		if (!strcmp (kvc->tdefs[i].name, name)) {
			return kvc->tdefs[i].type;
		}
	}
	return NULL;
}

static void emit_func_signature(KVCParser *kvc, const char *fn, RStrs fun_parm, bool is_static);

static void emit_func_args(KVCParser *kvc, const char *fname, const char *args) {
	emit_func_signature (kvc, fname, r_strs_from (args), false);
}

static void emit_func_typedef(KVCParser *kvc, const char *name, const char *rtype, const char *args) {
	if (R_STR_ISEMPTY (name)) {
		return;
	}
	emit_func_args (kvc, name, args);
	r_strbuf_appendf (kvc->sb, "func.%s.ret=%s\n", name, r_str_get_fail (rtype, "void"));
}

static int parse_ptr_depth(KVCParser *kvc) {
	int depth = 0;
	while (kvc_peek (kvc, 0) == '*') {
		kvc_getch (kvc);
		depth++;
		skip_spaces (kvc);
	}
	return depth;
}

static char *make_ptr_suffix(int depth) {
	if (depth <= 0) {
		return strdup ("");
	}
	RStrBuf *sb = r_strbuf_new ("");
	int i;
	for (i = 0; i < depth; i++) {
		r_strbuf_append (sb, " *");
	}
	return r_strbuf_drain (sb);
}

static bool emit_typedef_forward(KVCParser *kvc, const char *kind, const char *tag_str, int ptr_depth) {
	RStrs alias = { .a = consume_word (kvc) };
	if (!alias.a) {
		kvc_error (kvc, "Expected alias in typedef forward declaration");
		return false;
	}
	alias.b = kvc->s.a;
	char *alias_str = r_strs_tostring (alias);
	char *ptrs = make_ptr_suffix (ptr_depth);
	char *target = r_str_newf ("%s %s%s", kind, tag_str, ptrs);
	r_strbuf_appendf (kvc->sb, "typedef.%s=%s\n", alias_str, target);
	r_strbuf_appendf (kvc->sb, "%s=typedef\n", alias_str);
	kvc_register_typedef (kvc, alias_str, target);
	free (target);
	free (ptrs);
	free (alias_str);
	skip_ws (kvc);
	return true;
}

static char *lookahead_alias_after_brace(KVCParser *kvc, const char *anon_prefix) {
	const char *closing = kvc_find (kvc, "}");
	if (closing) {
		const char *p = closing + 1;
		while (p < kvc->s.b && (isspace ((unsigned char)*p) || *p == ';')) {
			p++;
		}
		const char *start = p;
		while (p < kvc->s.b && (isalnum ((unsigned char)*p) || *p == '_')) {
			p++;
		}
		if (p > start) {
			return r_str_ndup (start, p - start);
		}
	}
	return r_str_newf ("%s_%d", anon_prefix, kvc->line);
}

static bool parse_typedef(KVCParser *kvc, const char *unused) {
	skip_spaces (kvc);
	const char *next = kvc_peekn (kvc, 6);
	if (next && r_str_startswith (next, "struct")) {
		/* typedef struct [Tag]? { ... } Alias; */
		kvc_skipn (kvc, strlen ("struct"));
		skip_spaces (kvc);
		RStrs tag = { 0 };
		bool has_tag = false;
		if (*kvc->s.a != '{') {
			const char *tag_start = consume_word (kvc);
			if (!tag_start) {
				kvc_error (kvc, "Expected struct tag in typedef");
				return false;
			}
			tag = r_strs_new (tag_start, kvc->s.a);
			has_tag = true;
			skip_spaces (kvc);
		}
		if (kvc_peek (kvc, 0) != '{') {
			skip_spaces (kvc);
			int ptr_depth = parse_ptr_depth (kvc);
			char *tag_str = has_tag? r_strs_tostring (tag): strdup ("");
			bool res = emit_typedef_forward (kvc, "struct", tag_str, ptr_depth);
			free (tag_str);
			return res;
		}
		kvc_getch (kvc);
		char *struct_tag = has_tag? r_strs_tostring (tag): lookahead_alias_after_brace (kvc, "anon_struct");
		apply_attributes (kvc, "struct", struct_tag);
		RAnalBaseType *bt = r_anal_base_type_new (R_ANAL_BASE_TYPE_KIND_STRUCT);
		if (!bt) {
			free (struct_tag);
			return false;
		}
		bt->name = strdup (struct_tag);
		int off = 0;
		while (true) {
			skip_spaces (kvc);
			if (kvc_peek (kvc, 0) == '}') {
				kvc_getch (kvc); // Consume '}'
				break;
			}
			parse_attributes (kvc);
			skip_spaces (kvc);
			RStrs member_type = { 0 };
			RStrs member_name = { 0 };
			RStrs member_dimm = { 0 };
			// parse member type token up to semicolon
			member_type.a = kvc->s.a;
			member_type.b = scan_to_semicolon (kvc, false);
			if (!member_type.b) {
				kvc_error (kvc, "Missing semicolon in struct member");
				r_anal_base_type_free (bt);
				free (struct_tag);
				return false;
			}
			if (member_type.a == member_type.b) {
				kvc_getch (kvc);
				break;
			}
			member_name = member_type;
			token_typename (&member_type, &member_name);
#if 1
			kvc_getch (kvc); // Skip the semicolon
			parse_trailing_attributes (kvc); // Handle trailing /// comments on same line
#else
			// Handle trailing C-style __attribute__ before semicolon
			skip_spaces (kvc);
			while (parse_c_attributes (kvc)) {
				skip_spaces (kvc);
			}
			if (kvc_peek (kvc, 0) == ';') {
				kvc_getch (kvc);
			} else {
				kvc_error (kvc, "Expected ';' after struct field");
				return false;
			}
#endif
			r_strs_trim (&member_type);
			// Handle possible array dimensions (e.g. "[10]"):
			// Handle possible array dimensions (e.g. "[10]"):
			const char *bracket = r_strs_findc (member_name, '[');
			if (bracket) {
				// Extract dimension and adjust member name to exclude brackets
				member_dimm.a = bracket + 1;
				member_dimm.b = member_name.b;
				// Set name end to bracket start (exclusive) to include full name
				member_name.b = bracket;
				const char *close = r_strs_findc (member_dimm, ']');
				if (close) {
					member_dimm.b = close;
				} else {
					r_anal_base_type_free (bt);
					free (struct_tag);
					kvc_error (kvc, "Missing ] in struct member dimension");
					return false;
				}
			}
			char *mt = r_strs_tostring (member_type);
			char *mn = r_strs_tostring (member_name);
			char *md = r_strs_tostring (member_dimm);
			if (!*mn) {
				free (mt);
				free (mn);
				free (md);
				R_LOG_ERROR ("struct field parse failed");
				break;
			}
			r_strf_var (full_scope, 512, "%s.%s", struct_tag, mn);
			// Detect if this field is a function-pointer (direct or via typedef). If so,
			// skip the generic append here and let the specialized handling emit the
			// canonical named type and func.<struct>.<member> entries.
			bool _is_fp_field = r_strs_find_strs (member_type, R_STRS_LIT (" (*"));
			if (!_is_fp_field) {
				// check typedefs (mt is a heap string)
				const char *tdef_local = kvc_lookup_typedef (kvc, mt);
				if (tdef_local && (strstr (tdef_local, "* (") || strstr (tdef_local, " * ("))) {
					_is_fp_field = true;
				}
			}
			if (!_is_fp_field) {
				kvc_basetype_add_member (bt, mn, mt, off, R_STR_ISNOTEMPTY (md)? (int)r_num_math (NULL, md): 0);
				r_strbuf_appendf (kvc->sb, "struct.%s.%s.meta=0\n", struct_tag, mn);
				off += kvc_typesize (kvc, mt, 1);
				apply_attributes (kvc, "struct", full_scope);
				free (mt);
				free (mn);
				free (md);
				// continue with next field
				continue;
			}
			// function-pointer field: release temporary strings to avoid leaks
			free (mt);
			free (mn);
			free (md);
			continue;
		}
		// After the closing '}', we expect the typedef alias:
		skip_spaces (kvc);
		RStrs alias = { .a = consume_word (kvc) };
		if (!alias.a) {
			kvc_error (kvc, "Missing alias in typedef struct");
			r_anal_base_type_free (bt);
			free (struct_tag);
			return false;
		}
		alias.b = kvc->s.a;
		char *alias_str = r_strs_tostring (alias);
		r_strbuf_appendf (kvc->sb, "typedef.%s=struct %s\n", alias_str, struct_tag);
		skip_ws (kvc);
		kvc_basetype_emit (kvc, bt);
		r_strbuf_appendf (kvc->sb, "%s=struct\n", alias_str);
		free (struct_tag);
		free (alias_str);
		return true;
	} else if (next && r_str_startswith (next, "union")) {
		/* typedef union [Tag]? { ... } Alias; */
		kvc_skipn (kvc, strlen ("union"));
		skip_spaces (kvc);
		RStrs tag = { 0 };
		bool has_tag = false;
		if (*kvc->s.a != '{') {
			const char *tag_start = consume_word (kvc);
			if (!tag_start) {
				kvc_error (kvc, "Expected union tag in typedef");
				return false;
			}
			tag = r_strs_new (tag_start, kvc->s.a);
			has_tag = true;
			skip_spaces (kvc);
		}
		if (kvc_peek (kvc, 0) != '{') {
			skip_spaces (kvc);
			int ptr_depth = parse_ptr_depth (kvc);
			char *tag_str = has_tag? r_strs_tostring (tag): strdup ("");
			bool res = emit_typedef_forward (kvc, "union", tag_str, ptr_depth);
			free (tag_str);
			return res;
		}
		kvc_getch (kvc);
		char *union_tag = has_tag? r_strs_tostring (tag): lookahead_alias_after_brace (kvc, "anon_union");
		/* Begin output for the union definition */
		apply_attributes (kvc, "union", union_tag);
		RAnalBaseType *bt = r_anal_base_type_new (R_ANAL_BASE_TYPE_KIND_UNION);
		if (!bt) {
			free (union_tag);
			return false;
		}
		bt->name = strdup (union_tag);
		int off = 0;
		while (true) {
			skip_spaces (kvc);
			if (kvc_peek (kvc, 0) == '}') {
				kvc_getch (kvc); // Consume '}'
				break;
			}
			parse_attributes (kvc);
			skip_spaces (kvc);
			RStrs member_type = { 0 };
			RStrs member_name = { 0 };
			RStrs member_dimm = { 0 };
			// parse member type token up to semicolon
			member_type.a = kvc->s.a;
			member_type.b = scan_to_semicolon (kvc, false);
			if (!member_type.b) {
				kvc_error (kvc, "Missing semicolon in union member");
				r_anal_base_type_free (bt);
				free (union_tag);
				return false;
			}
			if (member_type.a == member_type.b) {
				kvc_getch (kvc);
				break;
			}
			member_name = member_type;
			token_typename (&member_type, &member_name);
			kvc_getch (kvc); // Skip the semicolon
			parse_trailing_attributes (kvc); // Handle trailing /// comments on same line
			r_strs_trim (&member_type);
			// Handle possible array dimensions (e.g. "[10]"):
			const char *bracket = r_strs_findc (member_name, '[');
			if (bracket) {
				// Extract dimension and adjust member name to exclude brackets
				member_dimm.a = bracket + 1;
				member_dimm.b = member_name.b;
				// Set name end to bracket start (exclusive) to include full name
				member_name.b = bracket;
				const char *close = r_strs_findc (member_dimm, ']');
				if (close) {
					member_dimm.b = close;
				} else {
					r_anal_base_type_free (bt);
					free (union_tag);
					kvc_error (kvc, "Missing ] in union member dimension");
					return false;
				}
			}
			char *mt = r_strs_tostring (member_type);
			char *mn = r_strs_tostring (member_name);
			char *md = r_strs_tostring (member_dimm);
			if (!*mn) {
				free (mt);
				free (mn);
				free (md);
				R_LOG_ERROR ("union field parse failed");
				break;
			}
			r_strf_var (full_scope, 512, "%s.%s", union_tag, mn);
			// Detect if this field is a function-pointer (direct or via typedef). If so,
			// skip the generic append here and let the specialized handling emit the
			// canonical named type and func.<union>.<member> entries.
			bool _is_fp_field = r_strs_find_strs (member_type, R_STRS_LIT (" (*"));
			if (!_is_fp_field) {
				// check typedefs (mt is a heap string)
				const char *tdef_local = kvc_lookup_typedef (kvc, mt);
				if (tdef_local && (strstr (tdef_local, "* (") || strstr (tdef_local, " * ("))) {
					_is_fp_field = true;
				}
			}
			if (!_is_fp_field) {
				kvc_basetype_add_member (bt, mn, mt, off, R_STR_ISNOTEMPTY (md)? (int)r_num_math (NULL, md): 0);
				apply_attributes (kvc, "union", full_scope);
				free (mt);
				free (mn);
				free (md);
				// continue with next field
				continue;
			}
			// function-pointer field: release temporary strings to avoid leaks
			free (mt);
			free (mn);
			free (md);
			continue;
		}
		// After the closing '}', we expect the typedef alias:
		skip_spaces (kvc);
		RStrs alias = { .a = consume_word (kvc) };
		if (!alias.a) {
			kvc_error (kvc, "Missing alias in typedef union");
			r_anal_base_type_free (bt);
			free (union_tag);
			return false;
		}
		alias.b = kvc->s.a;
		char *alias_str = r_strs_tostring (alias);
		r_strbuf_appendf (kvc->sb, "typedef.%s=union %s\n", alias_str, union_tag);
		skip_ws (kvc);
		kvc_basetype_emit (kvc, bt);
		r_strbuf_appendf (kvc->sb, "%s=union\n", alias_str);
		free (union_tag);
		free (alias_str);
		return true;
	} else if (next && r_str_startswith (next, "enum")) {
		/* typedef enum [Tag]? { ... } Alias; */
		kvc_skipn (kvc, strlen ("enum"));
		skip_spaces (kvc);
		RStrs tag = { 0 };
		bool has_tag = false;
		if (*kvc->s.a != '{') {
			const char *tag_start = consume_word (kvc);
			if (!tag_start) {
				kvc_error (kvc, "Expected enum tag in typedef");
				return false;
			}
			tag = r_strs_new (tag_start, kvc->s.a);
			has_tag = true;
			skip_spaces (kvc);
		}
		if (kvc_peek (kvc, 0) != '{') {
			skip_spaces (kvc);
			int ptr_depth = parse_ptr_depth (kvc);
			char *tag_str = has_tag? r_strs_tostring (tag): strdup ("");
			bool res = emit_typedef_forward (kvc, "enum", tag_str, ptr_depth);
			free (tag_str);
			return res;
		}
		kvc_getch (kvc);
		char *enum_tag = has_tag? r_strs_tostring (tag): lookahead_alias_after_brace (kvc, "anon_enum");
		r_strbuf_appendf (kvc->sb, "%s=enum\n", enum_tag);
		RStrBuf *enumstr = NULL;
		apply_attributes (kvc, "enum", enum_tag);
		ut64 value = 0;
		bool closing = false;
		while (!closing) {
			parse_attributes (kvc);
			skip_spaces (kvc);
			RStrs member_value = { 0 };
			const char *name_start = consume_word (kvc);
			if (!name_start) {
				R_LOG_ERROR ("a");
				free (enum_tag);
				return false;
			}
			RStrs member_name = r_strs_new (name_start, kvc->s.a);
			skip_spaces (kvc);
			char ch = kvc_getch (kvc);
			if (ch == '=') {
				skip_spaces (kvc);
				const char *val_start = consume_word (kvc);
				if (!val_start) {
					R_LOG_ERROR ("a");
					free (enum_tag);
					return false;
				}
				member_value = r_strs_new (val_start, kvc->s.a);
				skip_spaces (kvc);
				ch = kvc_getch (kvc);
				// equal
			}
			if (ch == '}') {
				closing = true;
			} else if (ch == ',') {
				// next
			} else {
				kvc_error (kvc, "Expected , or } inside enum");
				free (enum_tag);
				return false;
			}

			char *mn = r_strs_tostring (member_name);
			apply_attributes (kvc, "enum", enum_tag);
			r_strf_var (full_scope, 512, "%s.%s", enum_tag, mn);
			if (member_value.a) {
				st64 nv = (st64)r_strs_tonum (member_value, 0, NULL);
				r_strbuf_appendf (kvc->sb, "enum.%s=0x%" PFMT64x "\n", full_scope, nv);
				r_strbuf_appendf (kvc->sb, "enum.%s.0x%" PFMT64x "=%s\n", enum_tag, nv, mn);
				value = nv;
			} else {
				r_strbuf_appendf (kvc->sb, "enum.%s=0x%" PFMT64x "\n", full_scope, (ut64)value);
				r_strbuf_appendf (kvc->sb, "enum.%s.0x%" PFMT64x "=%s\n", enum_tag, (ut64)value, mn);
			}
			if (enumstr) {
				r_strbuf_appendf (enumstr, ",%s", mn);
			} else {
				enumstr = r_strbuf_new (mn);
			}
			free (mn);
			value++;
		}
		if (enumstr) {
			char *es = r_strbuf_drain (enumstr);
			r_strbuf_appendf (kvc->sb, "enum.%s=%s\n", enum_tag, es);
			free (es);
		}
		// After the closing '}', we expect the typedef alias:
		skip_spaces (kvc);
		RStrs alias = { .a = consume_word (kvc) };
		if (!alias.a) {
			kvc_error (kvc, "Missing alias in typedef enum");
			free (enum_tag);
			return false;
		}
		alias.b = kvc->s.a;
		char *alias_str = r_strs_tostring (alias);
		r_strbuf_appendf (kvc->sb, "typedef.%s=enum %s\n", alias_str, enum_tag);
		r_strbuf_appendf (kvc->sb, "%s=enum\n", alias_str);
		skip_ws (kvc);
		free (enum_tag);
		free (alias_str);
		return true;
	} else {
		/* Handle a “simple” typedef such as:
		typedef int myint;
		In this case we assume that everything from the current pointer until
		the semicolon is the declaration, and the last word is the alias. */
		const char *start = kvc->s.a;
		/* First check if this is a function-pointer typedef of the form:
		typedef RETTYPE (*alias) (ARGS); */
		RStrs decl = { .a = start };
		/* find semicolon for decl end */
		const char *semicolon = scan_to_semicolon (kvc, true);
		if (!semicolon) {
			semicolon = scan_to_semicolon (kvc, false);
		}
		decl.b = semicolon;
		if (!semicolon) {
			kvc_error (kvc, "Missing semicolon in typedef");
			return false;
		}
		/* Detect function-pointer typedefs like: typedef RET (*alias) (args); */
		const char *fp_marker = r_strs_find_strs (decl, R_STRS_LIT (" (*"));
		if (fp_marker) {
			const char *name_start = fp_marker + 3;
			const char *name_end = name_start;
			while (name_end < semicolon && *name_end != ')') {
				name_end++;
			}
			if (name_end < semicolon) {
				RStrs alias = r_strs_new (name_start, name_end);
				r_strs_trim (&alias);
				char *alias_str = r_strs_tostring (alias);
				RStrs rtype_tok = r_strs_new (start, fp_marker);
				r_strs_trim (&rtype_tok);
				char *rtype = r_strs_tostring (rtype_tok);
				/* find args */
				const char *args_open = name_end;
				while (args_open < semicolon && *args_open != '(') {
					args_open++;
				}
				char *args_str = NULL;
				if (args_open < semicolon) {
					const char *args_end = semicolon - 1;
					while (args_end >= args_open && *args_end != ')') {
						args_end--;
					}
					if (args_end > args_open) {
						args_str = collapse_whitespace (r_strs_new (args_open + 1, args_end));
					}
				}
				char *fulltype = r_str_newf ("%s * (%s)", rtype, r_str_get (args_str));
				// Map typedef alias to a canonical func.<alias> handle and emit func entries so the typedef
				// can be resolved even if it is never referenced by a struct field.
				r_strbuf_appendf (kvc->sb, "typedef.%s=func.%s\n", alias_str, alias_str);
				r_strbuf_appendf (kvc->sb, "%s=typedef\n", alias_str);
				emit_func_typedef (kvc, alias_str, rtype, args_str);
				// Keep the original fulltype registered so other code can detect function-pointer typedefs
				kvc_register_typedef (kvc, alias_str, fulltype);
				kvc_skipn (kvc, semicolon - kvc->s.a);
				if (kvc_peek (kvc, 0) == ';') {
					kvc_getch (kvc);
				}
				free (alias_str);
				free (rtype);
				free (args_str);
				free (fulltype);
				return true;
			}
		}
		const char *p = semicolon - 1;
		// Skip trailing spaces before alias
		while (p >= start && isspace (*p)) {
			p--;
		}
		// Mark end of alias
		const char *alias_end = p + 1;
		// Scan backwards over alias characters (alphanumeric and underscore)
		while (p >= start && (isalnum (*p) || *p == '_')) {
			p--;
		}
		// If stopped on non-identifier, advance to start of alias
		if (p < start || (!isalnum (*p) && *p != '_')) {
			p++;
		}
		RStrs alias = r_strs_new (p, alias_end);
		RStrs orig_type = r_strs_new (start, p);
		r_strs_trim (&alias);
		r_strs_trim (&orig_type);
		char *alias_str = r_strs_tostring (alias);
		char *type_str = r_strs_tostring (orig_type);
		r_strbuf_appendf (kvc->sb, "typedef.%s=%s\n", alias_str, type_str);
		r_strbuf_appendf (kvc->sb, "%s=typedef\n", alias_str);
		/* Register simple typedef for later lookup */
		kvc_register_typedef (kvc, alias_str, type_str);
		free (alias_str);
		free (type_str);
		kvc_skipn (kvc, semicolon - kvc->s.a);
		if (kvc_peek (kvc, 0) == ';') {
			kvc_getch (kvc);
		}
		return true;
	}
}

static bool split_fnptr_member(RStrs full, RStrs *name, RStrs *args, char **ret) {
	const char *p;
	for (p = full.a; p < full.b; p++) {
		if (*p != '(') {
			continue;
		}
		RStrs inner = r_strs_new (p + 1, full.b);
		r_strs_skip_chars (&inner, " \t\n\r\v\f");
		if (r_strs_at (inner, 0) != '*') {
			continue;
		}
		r_strs_skip_chars (&inner, "* \t\n\r\v\f");
		RStrs ident;
		do {
			ident = r_strs_take_ident (&inner);
			r_strs_skip_chars (&inner, " \t\n\r\v\f");
		} while (r_strs_equals_str (ident, "const") || r_strs_equals_str (ident, "volatile"));
		if (r_strs_empty (ident) || r_strs_at (inner, 0) != ')') {
			continue;
		}
		inner.a++;
		r_strs_skip_chars (&inner, " \t\n\r\v\f");
		if (r_strs_at (inner, 0) != '(') {
			continue;
		}
		const char *end = scan_toplevel (r_strs_new (inner.a + 1, full.b), ')');
		if (!end) {
			return false;
		}
		char *raw = r_str_newf ("%.*s%.*s", (int)(p - full.a), full.a,
			(int)(full.b - end - 1), end + 1);
		if (!raw) {
			return false;
		}
		*ret = collapse_whitespace (r_strs_from (raw));
		free (raw);
		*name = ident;
		*args = r_strs_new (inner.a + 1, end);
		return *ret != NULL;
	}
	return false;
}

// works for unions and structs
static bool parse_struct(KVCParser *kvc, const char *type) {
	RStrs struct_name = { .a = consume_word (kvc) };
	if (!struct_name.a) {
		R_LOG_ERROR ("Cannot consume word");
		return false;
	}
	struct_name.b = kvc->s.a;
	skip_spaces (kvc);
	parse_c_attributes (kvc);
	skip_spaces (kvc);
	const char p0 = kvc_peek (kvc, 0);
	if (p0 == ';') {
		// forward declaration: struct Foo; its attributes must not leak into the next declaration
		kvc->attrs.count = 0;
		kvc_getch (kvc);
		return true;
	}
	if (p0 != '{') {
		char *sn = r_strs_tostring (struct_name);
		R_LOG_ERROR ("Expected { after '%s' at line %d in struct", sn, kvc->line);
		free (sn);
		return false;
	}
	kvc_getch (kvc);
	char *sn = r_strs_tostring (struct_name);
	const bool is_union = !strcmp (type, "union");
	RAnalBaseType *bt = r_anal_base_type_new (is_union
		? R_ANAL_BASE_TYPE_KIND_UNION: R_ANAL_BASE_TYPE_KIND_STRUCT);
	if (!bt) {
		free (sn);
		return false;
	}
	bt->name = strdup (sn);
	const char *pack_attr = kvc_attr (kvc, "pack");
	const char *packed_attr = kvc_attr (kvc, "packed");
	if (pack_attr) {
		kvc->struct_pack = atoi (pack_attr);
	} else if (packed_attr) {
		kvc->struct_pack = 1;
	} else {
		kvc->struct_pack = 0;
	}
	free ((void *)pack_attr);
	free ((void *)packed_attr);
	apply_attributes (kvc, type, sn);
	// Lookahead: scan struct body for direct function-pointer members so we can
	// emit a typedef-like func handle for them (e.g. foo.fp=func)
	{
		const char *closing = kvc_find (kvc, "}");
		if (closing) {
			const char *p = kvc->s.a;
			while (p < closing) {
				const char *st = (const char *)r_mem_mem ((const ut8 *)p, closing - p, (const ut8 *)"(*", 2);
				if (!st) {
					break;
				}
				// find member name between '(*' and ')'
				const char *name_start = st + 2;
				const char *name_end = name_start;
				while (name_end < closing && *name_end != ')') {
					name_end++;
				}
				if (name_end >= closing) {
					p = name_end;
					continue;
				}
				RStrs mtok = r_strs_new (name_start, name_end);
				r_strs_trim (&mtok);
				char *mname_look = r_strs_tostring (mtok);
				if (mname_look) {
					const char *tdef = kvc_lookup_typedef (kvc, mname_look);
					if (tdef) {
						r_strbuf_appendf (kvc->sb, "%s.%s=func\n", sn, mname_look);
					}
					free (mname_look);
				}
				p = name_end + 1;
			}
		}
	}
	int off = 0;
	while (true) {
		parse_attributes (kvc);
		skip_spaces (kvc);
		RStrs member_type = { 0 };
		RStrs member_name = { 0 };
		RStrs member_dimm = { 0 };
		member_type.a = kvc->s.a;
		// Support function pointer fields: allow parentheses when scanning semicolon
		// only look inside this member, searching the rest of the input is quadratic
		const char *semi = r_strs_findc (kvc->s, ';');
		if (r_strs_find_strs ((RStrs){ member_type.a, semi? semi: kvc->s.b }, R_STRS_LIT ("(*"))) {
			member_type.b = scan_to_semicolon (kvc, true);
		} else {
			member_type.b = scan_to_semicolon (kvc, false);
		}
		if (!member_type.b) {
			// attempt extended scan allowing parentheses (attributes or function pointers)
			const char *semi2 = scan_to_semicolon (kvc, true);
			if (!semi2) {
				const char ch0 = kvc_peek (kvc, 0);
				if (ch0 == '}') {
					// end of struct definition
					kvc_getch (kvc);
					kvc_getch (kvc);
					break;
				}
				kvc_error (kvc, "Missing semicolon in struct member");
				r_anal_base_type_free (bt);
				free (sn);
				return false;
			}
			// check for C-style attribute inside this span
			const char *attrp = r_strs_find_strs ((RStrs){ member_type.a, semi2 }, R_STRS_LIT ("__attribute"));
			if (attrp) {
				member_type.b = attrp - 1;
				kvc->s.a = attrp;
				if (!parse_c_attributes (kvc)) {
					r_anal_base_type_free (bt);
					free (sn);
					return false;
				}
				skip_spaces (kvc);
			} else {
				// function pointer: include full type span
				member_type.b = semi2;
				kvc->s.a = semi2;
			}
		}
		if (member_type.a == member_type.b) {
			kvc_getch (kvc);
			break;
		}
		member_name = member_type;
		token_typename (&member_type, &member_name);
		skip_semicolons (kvc);
		parse_trailing_attributes (kvc); // Handle trailing /// comments on same line
		r_strs_trim (&member_type);
		RStrs fn_name, fn_args;
		char *rtype = NULL;
		if (split_fnptr_member (member_type, &fn_name, &fn_args, &rtype)) {
			char *mname = r_strs_tostring (fn_name);
			char *args = collapse_whitespace (fn_args);
			char *type_name = r_str_newf ("%s.%s", sn, mname);
			if (!is_union) {
				kvc_align_offset (kvc, &off, kvc->ptr_size);
			}
			kvc_basetype_add_member (bt, mname, type_name, off, 0);
			emit_func_args (kvc, type_name, args);
			r_strbuf_appendf (kvc->sb, "type.%s=func\n", type_name);
			r_strbuf_appendf (kvc->sb, "%s=func\n", type_name);
			r_strbuf_appendf (kvc->sb, "func.%s.ret=%s\n", type_name, r_str_get_fail (rtype, "void"));
			if (!is_union) {
				off += kvc->ptr_size;
			}
			apply_attributes (kvc, "struct", type_name);
			free (type_name);
			free (rtype);
			free (args);
			free (mname);
			continue;
		}

		// Check for typedef aliases that represent function pointers
		{
			char *mt_check = r_strs_tostring (member_type);
			const char *tdef = kvc_lookup_typedef (kvc, mt_check);
			if (tdef) {
				/* If typedef stored a function-pointer like "int * (...)" treat it as function pointer */
				if (strstr (tdef, "* (") || strstr (tdef, " * (")) {
					char *mname = r_strs_tostring (member_name);
					// split tdef into rtype and args
					const char *p = strstr (tdef, "* (");
					if (!p) {
						p = strstr (tdef, " * (");
					}
					if (p) {
						RStrs rtype_tok = { tdef, p };
						r_strs_trim (&rtype_tok);
						char *rtype = r_strs_tostring (rtype_tok);
						// find args
						const char *args_open = strchr (p, '(');
						char *args_str = NULL;
						if (args_open) {
							const char *args_close = strrchr (tdef, ')');
							if (args_close && args_close > args_open) {
								args_str = collapse_whitespace (r_strs_new (args_open + 1, args_close));
							}
						}
						// For typedef function-pointer types, reference the typedef alias as the field's type
						if (!strcmp (type, "struct")) {
							kvc_align_offset (kvc, &off, kvc->ptr_size);
						}
						kvc_basetype_add_member (bt, mname, mt_check, off, 0);
						if (!is_union) {
							off += kvc_typesize (kvc, tdef, 1);
						}
						{
							r_strf_var (full_scope, 512, "%s.%s", sn, mname);
							apply_attributes (kvc, "struct", full_scope);
						}
						emit_func_args (kvc, mt_check, args_str);
						r_strbuf_appendf (kvc->sb, "func.%s.ret=%s\n", mt_check, rtype);
						free (mname);
						free (rtype);
						free (args_str);
						free (mt_check);
						continue;
					}
				}
			}
			free (mt_check);
		}
		if (member_name.a) {
			const char *bracket = r_strs_findc (member_name, '[');
			if (bracket) {
				// parse dimensions
				member_dimm.a = bracket + 1;
				member_dimm.b = member_name.b;
				member_name.b = member_dimm.a - 1;
				member_dimm.b = r_strs_findc (member_dimm, ']');
				if (member_dimm.b) {
					// Dimensions already consumed by kvc_find_semicolon; no need to skip
				} else {
					R_LOG_ERROR ("Missing ] in struct field dimension");
				}
			}
		}

		char *mt = r_strs_tostring (member_type);
		char *mn = r_strs_tostring (member_name);
		char *md = r_strs_tostring (member_dimm);
		if (!*mn) {
			kvc_error (kvc, "Missing type, name or dimension in struct field");
			free (mt);
			free (mn);
			free (md);
			break;
		}
		massage_type (&mt);
		r_strf_var (full_scope, 512, "%s.%s", sn, mn);
		int dimension = 1;
		const char *align_attribute = kvc_attr (kvc, "aligned");
		int av = kvc_typealign (kvc, mt);
		if (align_attribute) {
			int explicit_align = atoi (align_attribute);
			if (explicit_align > 0) {
				av = explicit_align;
			}
			free ((void *)align_attribute);
		}
		if (kvc->struct_pack > 0 && av > kvc->struct_pack) {
			av = kvc->struct_pack;
		}
		if (av > 1 && (off % av)) {
			const int rest = av - (off % av);
			off += rest;
		}
		if (R_STR_ISNOTEMPTY (md)) {
			dimension = (int)r_num_math (NULL, md);
		}
		kvc_basetype_add_member (bt, mn, mt, off, R_STR_ISNOTEMPTY (md)? dimension: 0);
		if (!is_union) {
			off += kvc_typesize (kvc, mt, dimension);
		}
		// r_strbuf_appendf (kvc->sb, "%s.%s.meta=0\n", type, mn);
		apply_attributes (kvc, type, full_scope);
		free (mt);
		free (mn);
		free (md);
	}
	skip_ws (kvc);
	kvc_basetype_emit (kvc, bt);
	free (sn);
	return true;
}

static bool parse_enum(KVCParser *kvc, const char *name) {
	parse_attributes (kvc);
	RStrs enum_name = { .a = consume_word (kvc) };
	if (!enum_name.a) {
		R_LOG_ERROR ("Cannot consume a word");
		return false;
	}
	enum_name.b = kvc->s.a;
	char *en = r_strs_tostring (enum_name);
	r_strbuf_appendf (kvc->sb, "%s=enum\n", en);
	RStrBuf *enumstr = NULL;
	apply_attributes (kvc, "enum", en);
	skip_spaces (kvc);
	const char p0 = kvc_peek (kvc, 0);
	if (p0 != '{') {
		R_LOG_ERROR ("Expected { after '%s' at line %d in enum", en, kvc->line);
		free (en);
		return false;
	}
	kvc_getch (kvc);
	ut64 value = 0;
	bool closing = false;
	while (!closing) {
		parse_attributes (kvc);
		skip_spaces (kvc);
		RStrs member_value = { 0 };
		const char *name_start = consume_word (kvc);
		if (!name_start) {
			R_LOG_ERROR ("a");
			return false;
		}
		RStrs member_name = r_strs_new (name_start, kvc->s.a);
		skip_spaces (kvc);
		char ch = kvc_getch (kvc);
		if (ch == '=') {
			skip_spaces (kvc);
			const char *val_start = consume_word (kvc);
			if (!val_start) {
				R_LOG_ERROR ("a");
				return false;
			}
			member_value = r_strs_new (val_start, kvc->s.a);
			skip_spaces (kvc);
			ch = kvc_getch (kvc);
			// equal
		}
		if (ch == '}') {
			closing = true;
		} else if (ch == ',') {
			// next
		} else {
			kvc_error (kvc, "Expected , or } inside enum");
			free (en);
			return false;
		}

		char *mn = r_strs_tostring (member_name);
		apply_attributes (kvc, "enum", en);
		r_strf_var (full_scope, 512, "%s.%s", en, mn);
		if (member_value.a) {
			st64 nv = (st64)r_strs_tonum (member_value, 0, NULL);
			r_strbuf_appendf (kvc->sb, "enum.%s=0x%" PFMT64x "\n", full_scope, nv);
			r_strbuf_appendf (kvc->sb, "enum.%s.0x%" PFMT64x "=%s\n", en, nv, mn);
			value = nv;
		} else {
			r_strbuf_appendf (kvc->sb, "enum.%s=0x%" PFMT64x "\n", full_scope, (ut64)value);
			r_strbuf_appendf (kvc->sb, "enum.%s.0x%" PFMT64x "=%s\n", en, (ut64)value, mn);
		}
		if (enumstr) {
			r_strbuf_appendf (enumstr, ",%s", mn);
		} else {
			enumstr = r_strbuf_new (mn);
		}
		free (mn);
		value++;
	}
	if (enumstr) {
		char *es = r_strbuf_drain (enumstr);
		r_strbuf_appendf (kvc->sb, "enum.%s=%s\n", en, es);
		free (es);
	}
	skip_ws (kvc);
	free (en);
	return true;
}

static bool split_fnptr_param(RStrs full, char **type, char **name) {
	const char *open = r_strs_findc (full, '(');
	if (!open) {
		return false;
	}
	RStrs inner = r_strs_new (open + 1, full.b);
	r_strs_skip_chars (&inner, " \t\n\r\v\f");
	if (r_strs_at (inner, 0) != '*') {
		return false;
	}
	r_strs_skip_chars (&inner, "* \t\n\r\v\f");
	RStrs prefix = r_strs_new (full.a, inner.a);
	r_strs_trim (&prefix);
	RStrs ident;
	do {
		ident = r_strs_take_ident (&inner);
		r_strs_skip_chars (&inner, " \t\n\r\v\f");
	} while (r_strs_equals_str (ident, "const") || r_strs_equals_str (ident, "volatile"));
	if (r_strs_at (inner, 0) != ')') {
		return false;
	}
	RStrs suffix = r_strs_new (inner.a, full.b);
	// Commas delimit SDB argument rows, so collapse nested multi-argument types.
	char *raw_type;
	if (r_strs_findc (suffix, ',')) {
		raw_type = r_str_newf ("%.*s)()", (int)r_strs_len (prefix), prefix.a);
	} else {
		raw_type = r_str_newf ("%.*s%.*s", (int)r_strs_len (prefix), prefix.a,
			(int)r_strs_len (suffix), suffix.a);
	}
	if (!raw_type) {
		return false;
	}
	*type = collapse_whitespace (r_strs_from (raw_type));
	free (raw_type);
	*name = r_strs_empty (ident)? NULL: r_strs_tostring (ident);
	if (!*type || (!r_strs_empty (ident) && !*name)) {
		free (*type);
		free (*name);
		*type = *name = NULL;
		return false;
	}
	return true;
}

static void emit_func_signature(KVCParser *kvc, const char *fn, RStrs fun_parm, bool is_static) {
	RStrBuf *func_args_sb = r_strbuf_new ("");
	int arg_idx = 0;
	RStrs parms = fun_parm;
	r_strs_trim (&parms);
	// C spells the empty parameter list as (void); () and ( ) trim down to nothing
	if (!r_strs_empty (parms) && !r_strs_equals_str (parms, "void")) {
		while (!r_strs_empty (parms)) {
			const char *comma = scan_toplevel (parms, ',');
			RStrs full = r_strs_new (parms.a, comma? comma: parms.b);
			parms.a = comma? comma + 1: parms.b;
			r_strs_trim (&full);
			RStrs arg_type = full;
			RStrs arg_name = full;
			token_typename (&arg_type, &arg_name);
			char *an = r_strs_tostring (arg_name);
			char *at = r_strs_tostring (arg_type);
			if (r_strs_equals_str (full, "...")) {
				// canonical types.sdb.txt form: empty type, "..." as the name
				free (at);
				at = strdup ("");
				free (an);
				an = r_strs_tostring (full);
			} else if (r_strs_findc (full, '(')) {
				char *ft = NULL;
				char *fnm = NULL;
				if (split_fnptr_param (full, &ft, &fnm)) {
					free (at);
					at = ft;
					free (an);
					an = fnm; // NULL when unnamed, renamed to argN below
				}
			} else if (!r_strs_len (arg_type)) {
				free (at);
				at = r_strs_tostring (full);
				free (an);
				an = NULL; // named argN by the fallback below
			}
			if (at && !strchr (at, '(')) {
				massage_type (&at);
			}
			if (R_STR_ISEMPTY (an)) {
				free (an);
				an = r_str_newf ("arg%d", arg_idx);
			}
			if (an) {
				r_strbuf_appendf (kvc->sb, "func.%s.arg.%d=%s,%s\n", fn, arg_idx, at, an);
				r_strbuf_appendf (func_args_sb, "%s%s", arg_idx? ",": "", an);
				free (an);
			}
			free (at);
			arg_idx++;
		}
	}
	char *func_args = r_strbuf_drain (func_args_sb);
	r_strbuf_appendf (kvc->sb, "func.%s.cc=cdecl\n", fn);
	r_strbuf_appendf (kvc->sb, "func.%s=%s\n", fn, func_args);
	r_strbuf_appendf (kvc->sb, "func.%s.args=%d\n", fn, arg_idx);
	if (is_static) {
		r_strbuf_appendf (kvc->sb, "func.%s.@.static=true\n", fn);
	}
	free (func_args);
}

static void emit_func_decl(KVCParser *kvc, const char *fn, const char *fr, RStrs fun_parm, bool is_static) {
	r_strbuf_appendf (kvc->sb, "%s=func\n", fn);
	apply_attributes (kvc, "func", fn);
	emit_func_signature (kvc, fn, fun_parm, is_static);
	r_strbuf_appendf (kvc->sb, "func.%s.ret=%s\n", fn, fr);
}

static bool parse_function(KVCParser *kvc) {
	parse_attributes (kvc);
	bool is_static = false;
	skip_spaces (kvc);
	const char *word = kvc_peekn (kvc, 7);
	if (word && !strncmp (word, "static", 6) && (isspace (word[6]) || word[6] == 0 || word[6] == ';')) {
		kvc_skipn (kvc, 6);
		skip_spaces (kvc);
		is_static = true;
	}
	RStrs fun_name = { 0 };
	RStrs fun_parm = { 0 };
	const char *saved_pos = kvc->s.a;
	const char *rtyp_start = consume_word (kvc);
	if (!rtyp_start) {
		return false;
	}
	RStrs fun_rtyp = r_strs_new (rtyp_start, kvc->s.a);
	fun_name.a = rtyp_start;
	skip_spaces (kvc);
	if (kvc_peek (kvc, 0) == '(' && kvc_peek (kvc, 1) == '*') {
		kvc_skipn (kvc, 2);
		skip_spaces (kvc);
		const char *fp_start = consume_word (kvc);
		if (!fp_start) {
			kvc->s.a = saved_pos;
			return false;
		}
		RStrs fp_name = r_strs_new (fp_start, kvc->s.a);
		skip_spaces (kvc);
		if (kvc_peek (kvc, 0) != ')') {
			kvc->s.a = saved_pos;
			return false;
		}
		kvc_getch (kvc);
		skip_spaces (kvc);
		if (kvc_peek (kvc, 0) != '(') {
			kvc->s.a = saved_pos;
			return false;
		}
		if (!capture_paren_group (kvc, &fun_parm)) {
			kvc->s.a = saved_pos;
			return false;
		}
		skip_ws (kvc);
		char *fn = r_strs_tostring (fp_name);
		char *fr = r_strs_tostring (fun_rtyp);
		emit_func_decl (kvc, fn, fr, fun_parm, is_static);
		free (fn);
		free (fr);
		return true;
	}
	if (!skip_until (kvc, '(')) {
		kvc_error (kvc, "Cannot find ( in function definition");
		return false;
	}
	fun_name.b = kvc->s.a;
	if (!capture_paren_group (kvc, &fun_parm)) {
		kvc_error (kvc, "Cannot find ) in function definition");
		return false;
	}
	token_typename (&fun_rtyp, &fun_name);
	skip_ws (kvc);
	char *fn = r_strs_tostring (fun_name);
	char *fr = r_strs_tostring (fun_rtyp);
	emit_func_decl (kvc, fn, fr, fun_parm, is_static);
	free (fn);
	free (fr);
	return true;
}

static void kvcparser_init(KVCParser *kvc, const char *data) {
	kvc->line = 1;
	kvc->sb = r_strbuf_new ("");
	kvc->s.a = data;
	kvc->s.b = data + strlen (data);
}

static void kvcparser_fini(KVCParser *kvc) {
	int i;
	for (i = 0; i < (int)kvc->tdef_count; i++) {
		free (kvc->tdefs[i].name);
		free (kvc->tdefs[i].type);
	}
	kvc->tdef_count = 0;
	r_strbuf_free (kvc->sb);
	free (kvc->error_msg);
}

static bool tryparse(KVCParser *kvc, const char *word, const char *type, KVCParserCallback cb) {
	if (r_str_startswith (word, type)) {
		kvc_skipn (kvc, strlen (type));
		const char ch = kvc_getch (kvc);
		if (isspace (ch)) {
			skip_spaces (kvc);
			return cb? cb (kvc, type): true;
		}
	}
	return false;
}

R_IPI char *kvc_parse(const char *header_content, int ptr_size, char **errmsg) {
	// Initialize a preprocessing state for this parse
	PPState *pps = pp_new ();
	char *pre = pp_preprocess (pps, header_content);
	if (!pre) {
		pp_free (pps);
		return NULL;
	}
	pp_free (pps);
	KVCParser _kvc = { 0 };
	KVCParser *kvc = &_kvc;
	kvcparser_init (&_kvc, pre);
	kvc->ptr_size = ptr_size > 0? ptr_size: 8;
	while (!r_strs_empty (kvc->s)) {
		skip_ws (kvc);
		const char *word = kvc_peekn (kvc, 6);
		bool hasparse = false;
		if (word) {
#if 1
			hasparse = tryparse (kvc, word, "typedef", parse_typedef);
			hasparse |= tryparse (kvc, word, "struct", parse_struct);
			hasparse |= tryparse (kvc, word, "union", parse_struct);
			hasparse |= tryparse (kvc, word, "enum", parse_enum);
#else
			hasparse = tryparse (kvc, word, "typedef", parse_typedef);
			if (!hasparse) {
				hasparse = tryparse (kvc, word, "struct", parse_struct);
			}
			if (!hasparse) {
				hasparse = tryparse (kvc, word, "union", parse_struct);
			}
			if (!hasparse) {
				hasparse = tryparse (kvc, word, "enum", parse_enum);
			}
#endif
		}
		if (hasparse) {
			continue;
		}
		if (parse_attributes (kvc)) {
			continue;
		}
		if (!parse_function (kvc)) {
			kvc_getch (kvc);
		}
	}
	char *res = NULL;
	if (kvc->error && errmsg) {
		*errmsg = strdup (r_str_get_fail (kvc->error_msg, kvc->error));
	}
	if (!kvc->error) {
		res = r_strbuf_drain (kvc->sb);
		kvc->sb = NULL;
	}
	kvcparser_fini (kvc);
	free (pre);
	return res;
}

#if MAIN
int main(int argc, char *argv[]) {
	if (argc < 2) {
		eprintf ("Usage: %s <header_file.h>\n", argv[0]);
		return 1;
	}

	char *content = r_file_slurp (argv[1], NULL);
	if (!content) {
		R_LOG_ERROR ("Failed to read file: %s", argv[1]);
		return 1;
	}

	char *errmsg = NULL;
	char *result = kvc_parse ((const char *)content, 8, &errmsg);
	if (errmsg) {
		R_LOG_ERROR ("Parsing problem %s", errmsg);
		free (errmsg);
	}
	if (result) {
		printf ("%s\n", result);
		free (result);
	}

	free (content);
	return 0;
}
#endif
