/*
 * Copyright (c) 2003 - 2007, Nils R. Weller
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * 1. Redistributions of source code must retain the above copyright
 * notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 * notice, this list of conditions and the following disclaimer in the
 * documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 *
 * This file contains the functions and data needed to parse GNU-C-style
 * __attribute__ arguments
 */ 

#include "attribute.h"
#include <string.h>
#include <stdlib.h>
#include "token.h"
#include "misc.h"
#include "scope.h"
#include "symlist.h"
#include "backend.h"
#include "expr.h"
#include "type.h"
#include "decl.h"
#include "typemap.h"
#include "error.h"
#include "debug.h"
#include "n_libc.h"

#define A_OK		1
#define A_UNIMPL	2
#define A_IGNORED	3

struct name_val_pair {
	char	*name;
	int	value;
};

struct attrarg {
	int			type;
	int			extended_type;
	struct name_val_pair	*identifiers;
};

struct name_val_pair	mode_strings[] = {
	{ "QI", ATTR_MODE_DI },
	{ "DI", ATTR_MODE_DI },
	{ "SI", ATTR_MODE_SI },
	{ "HI", ATTR_MODE_SI },
	{ "byte", ATTR_MODE_SI },
	{ "word", ATTR_MODE_SI },
	{ NULL, 0 }
};
struct attrarg		mode_args[] = {
	{ TOK_IDENTIFIER, 0, mode_strings }
};
struct attrarg		aligned_args[] = {
	{ TY_INT, 0, NULL }
};


static struct attrent {
	char		*name;
	int		value;
	int		type;
	int		nargs; /* 0 = no args, -1 = variable number */
	int		ignorable;
	unsigned	fastattrflag;
	struct attrarg	*argdata;
	int		warned;
} attrtab[] = {
	{ "alias", ATTRF_ALIAS, A_UNIMPL, 0, 0, 0, NULL, 0 },
	{ "aligned", ATTRS_ALIGNED, A_OK, 1, 0, CATTR_ALIGNED, aligned_args, 0 },
	{ "always_inline", ATTRF_ALWAYS_INLINE, A_UNIMPL, 0, 1, 0, NULL, 0 },
	{ "bounded", ATTRV_BOUNDED, A_IGNORED, -1, 1, 0, NULL, 0 },
	{ "cdecl", ATTRF_CDECL, A_UNIMPL, 0, 0, 0, NULL, 0 },
	{ "cleanup", ATTRV_CLEANUP, A_UNIMPL, 0, 0, 0, NULL, 0 },
	{ "common", ATTRV_COMMON, A_UNIMPL, 0, 0, 0, NULL, 0 },
	{ "const", ATTRF_CONST, A_IGNORED, 0, 1, 0, NULL, 0 },	
	{ "constructor", ATTRF_CONSTRUCTOR, A_UNIMPL, 0, 0, 0, NULL, 0 },
	{ "deprecated", ATTRS_DEPRECATED, A_IGNORED, 0, 1, 0, NULL, 0 },
	{ "destructor", ATTRF_DESTRUCTOR, A_UNIMPL, 0, 0, 0, NULL, 0 },
	{ "dllimport", ATTRV_DLLIMPORT, A_UNIMPL, 0, 0, 0, NULL, 0 },	
	{ "dllexport", ATTRV_DLLEXPORT, A_UNIMPL, 0, 0, 0, NULL, 0 },	
	{ "eightbit_data", ATTRF_EIGHTBIT_DATA, A_UNIMPL, 0, 0, 0, NULL, 0 },
	{ "exception_handler", ATTRF_EXCEPTION_HANDLER, A_UNIMPL, 0, 0, 0, NULL, 0 },
	{ "externally_visible", ATTRF_EXTERNALLY_VISIBLE, A_IGNORED, 0, 1, 0, NULL, 0 },
	{ "far", ATTRF_FAR, A_UNIMPL, 0, 0, 0, NULL, 0 },
	{ "fastcall", ATTRF_FASTCALL, A_UNIMPL, 0, 0, 0, NULL, 0 },
	{ "flatten", ATTRF_FLATTEN, A_IGNORED, 0, 1, 0, NULL, 0 },
	{ "force_align_arg_pointer", ATTRF_FORCE_ALIGN_ARG_POINTER, A_UNIMPL, 0, 0, 0, NULL, 0 },
	{ "format", ATTRF_FORMAT, A_IGNORED, 3, 0, CATTR_FORMAT, NULL, 0 },
	{ "format_arg", ATTRF_FORMAT_ARG, A_IGNORED, 1, 1, 0,NULL, 0 },
	{ "function_vector", ATTRF_FUNCTION_VECTOR, A_UNIMPL, 0, 0, 0, NULL, 0 },
	{ "interrupt", ATTRF_INTERRUPT, A_UNIMPL, 0, 0, 0, NULL, 0 },
	{ "interrupt_handler", ATTRF_INTERRUPT_HANDLER, A_UNIMPL, 0, 0, 0, NULL, 0 },
	{ "longcall", ATTRF_LONGCALL, A_UNIMPL, 0, 0, 0, NULL, 0 },
	{ "long_call", ATTRF_LONG_CALL, A_UNIMPL, 0, 0, 0, NULL, 0 },
	{ "gnu_inline", ATTRF_GNU_INLINE, A_UNIMPL, 0, 0, 0, NULL, 0 },
	{ "kspisusp", ATTRF_KSPISUSP, A_UNIMPL, 0, 0, 0, NULL, 0 },
	{ "malloc", ATTRF_MALLOC, A_IGNORED, 0, 1, 0, NULL, 0 },
	{ "may_alias", ATTRS_MAY_ALIAS, A_UNIMPL, 0, 0, 0, NULL, 0 },
	{ "mode", ATTRV_MODE, A_OK, 1, 0, CATTR_MODE, mode_args, 0 },
	{ "model", ATTRV_MODEL, A_UNIMPL, 0, 0, 0, NULL, 0 },
	{ "naked", ATTRF_NAKED, A_UNIMPL, 0, 0, 0, NULL, 0 },
	{ "near", ATTRF_NEAR, A_UNIMPL, 0, 0, 0, NULL, 0 },
	{ "nesting", ATTRF_NESTING, A_UNIMPL, 0, 0, 0, NULL, 0 },
	{ "nmi_handler", ATTRF_NMI_HANDLER, A_UNIMPL, 0, 0, 0, NULL, 0 },
	{ "no_instrument_function",
		ATTRF_NO_INSTRUMENT_FUNCTION, A_UNIMPL, 0, 0, 0, NULL, 0 },
	{ "nocommon", ATTRV_NOCOMMON, A_UNIMPL, 0, 0, 0, NULL, 0 },
	{ "noinline", ATTRF_NOINLINE, A_IGNORED, 0, 1, 0, NULL, 0 },
	{ "nonnull", ATTRF_NONNULL, A_IGNORED, 0, 1, 0, NULL, 0 },
	{ "noreturn", ATTRF_NORETURN, A_IGNORED, 0, 1, 0, NULL, 0 },
	{ "nothrow", ATTRF_NOTHROW, A_IGNORED, 0, 0, 0, NULL, 0 },
	{ "packed", ATTRS_PACKED, A_UNIMPL, 0, 0, CATTR_PACKED, NULL, 0 },
	{ "pure", ATTRF_PURE, A_IGNORED, 0, 1, CATTR_PURE, NULL, 0 },
	{ "regparm", ATTRF_REGPARM, A_UNIMPL, 1, 0, 0, NULL, 0 },
	{ "returns_twice", ATTRF_RETURNS_TWICE, A_UNIMPL, 0, 0, 0, NULL, 0 },
	{ "saveall", ATTRF_SECTION, A_UNIMPL, 0, 0, 0, NULL, 0 },
	{ "section", ATTRF_SECTION, A_UNIMPL, 0, 0, CATTR_SECTION, NULL, 0 },
	{ "sentinel", ATTRF_SENTINEL, A_IGNORED, 0, 1, 0, NULL, 0 },
	{ "shared", ATTRV_SHARED, A_UNIMPL, 0, 0, 0, NULL, 0 },
	{ "shortcall", ATTRF_SHORTCALL, A_UNIMPL, 0, 0, 0, NULL, 0 },
	{ "short_call", ATTRF_SHORT_CALL, A_UNIMPL, 0, 0, 0, NULL, 0 },
	{ "signal", ATTRF_SIGNAL, A_UNIMPL, 0, 0, 0, NULL, 0 },
	{ "sp_switch", ATTRF_SP_SWITCH, A_UNIMPL, 0, 0, 0, NULL, 0 },
	{ "sseregparam", ATTRF_SSEREGPARAM, A_UNIMPL, 0, 0, 0, NULL, 0 },
	{ "stdcall", ATTRF_STDCALL, A_UNIMPL, 0, 0, 0, NULL, 0 },
	{ "tiny_data", ATTRF_TINY_DATA, A_UNIMPL, 0, 0, 0, NULL, 0 },
	{ "tls_model", ATTRV_TLS_MODEL, A_UNIMPL, 0, 0, 0, NULL, 0 },
	{ "transparent_union", ATTRS_TRANSPARENT_UNION, A_OK, 0, 0, CATTR_TRANSPARENT_UNION, NULL, 0 },
	{ "trap_exit", ATTRF_TRAP_EXIT, A_UNIMPL, 0, 0, 0, NULL, 0 },
 	{ "unused", ATTRF_UNUSED, A_IGNORED, 0, 1, CATTR_UNUSED, NULL, 0 },
	{ "used", ATTRF_USED, A_IGNORED, 0, 1, CATTR_USED, NULL, 0 },
	{ "vector_size", ATTRV_VECTOR_SIZE, A_UNIMPL, 0, 0, 0, NULL, 0 },
	{ "visibility", ATTRF_VISIBILITY, A_UNIMPL, 0, 0, 0, NULL, 0 },
	{ "warn_unused_result", ATTRF_WEAK, A_IGNORED, 0, 1, 0, NULL, 0 },
	{ "weak", ATTRF_WEAK, A_UNIMPL, 0, 0, CATTR_WEAK, NULL, 0 },
	{ "weakref", ATTRF_WEAK, A_UNIMPL, 0, 0, 0, NULL, 0 },
	{ 0, 0, 0, 0, 0, 0, NULL, 0 }
};

static struct attrent *
find_attrent(char *name) {
	int	i;
	size_t	len = 0;
	char	*p = NULL;

	/*
	 * All attribute specifiers might either have the form ``attr'' or
	 * ``__attr__'', or ``__attr'', or ...????? so we have to determine
	 * which is the case here
	 */
	if (*name == '_') {
		if (name[1] != '_') {
			return NULL;
		}
		p = name + 2;
		if ((len = strlen(p)) < 3) {
			/* Bogus attribute, becase it can't match __*X*__ */
			return NULL;
		}
		if (p[len - 1] != '_' || p[len - 2] != '_') {
			/* Doesn't end with __ */
			;
		} else {
			/* Ends with __ */
			len -= 2;
		}
	}

	for (i = 0; attrtab[i].name != NULL; ++i) {
		if (p == NULL) {
			/* No trailing __ to take into account */
			if (strcmp(attrtab[i].name, name) == 0) {
				/* Found */
				return &attrtab[i];
			}
		} else if (strncmp(attrtab[i].name, p, len) == 0) {
			/* Found */
			return &attrtab[i];
		}
	}
	/* Not found */
	return NULL;
}
	

static void
free_attrib(struct attrib *attr) {
	struct attrib	*at;

	while (attr) {
		at = attr;
		attr = attr->next;
		free(at);
	}
}


static struct attrib *
alloc_attrib(void) {
	struct attrib	*ret = n_xmalloc(sizeof *ret);
	static struct attrib	nullattrib;
	*ret = nullattrib;
	return ret;
}	

void
append_attribute(struct attrib **head, struct attrib *attr) {
	if (*head == NULL) {
		*head = attr;
	} else {
		/* Check whether this is a duplicated attribute */
		struct attrib	*tmp;
		struct attrib	*lastnode = NULL;

		for (tmp = *head; tmp != NULL; tmp = tmp->next) {
			if (tmp->next == NULL) {
				lastnode = tmp; 
			}
			if (tmp->code == attr->code) {
				errorfl(attr->tok,
					"Duplicated attribute");
				return;
			}
		}

		/* Append */
		lastnode->next = attr;
	}
	attr->next = NULL;
}

struct attrib *
get_single_attribute(struct token **tok, int *fatalerror) {
	struct token	*t;
	struct token	*start;
	struct attrent	*at;
	struct attrib	*ret;
	char		*attrname;
	int		parens = 0;
	struct token	*attrargs[128];
	int		intargs[128];
	struct attrarg	*arginfo;
	int		argidx = 0;
	int		error = 0;
	int		i;

	*fatalerror = 0;
#define N_ATTRARGS (sizeof attrargs / sizeof attrargs[0])
	
	t = start = *tok;
	attrname = t->data;
	if ((at = find_attrent(attrname)) == NULL) {
		warningfl(t, "Unknown attribute `%s'", attrname);
		*fatalerror = 1;
		return NULL;
	}

	if (next_token(&t) != 0) {
		*fatalerror = 1;
		return NULL;
	}

	if ((t->type == TOK_OPERATOR
		&& *(int *)t->data == TOK_OP_COMMA)
		|| t->type == TOK_PAREN_CLOSE) {
		/*
		 * This is a comma or parentheses, so the current
		 * attribute already ends here. Is that OK?
		 */
		if (at->nargs != 0 && at->nargs != -1) {
			/* Error! (but only warn) */
			warningfl(start, "Not enough arguments for "
				"attribute `%s' - ignoring", attrname);
			return NULL;
		} else {
			t = t->prev;
			goto skipargs;
		}
	}	


	/* Read arguments to attribute */
	if (t->type != TOK_PAREN_OPEN) {
		warningfl(t, "Syntax error at `%s', expected "
			"opening parentheses",
			t->ascii);
		*fatalerror = 1;
		return NULL;
	}
		
	if (next_token(&t) != 0) {
		return NULL;
	}
	++parens;

	for (; t != NULL; t = t->next) {
		int	is_paren = 1;

		if (t->type == TOK_PAREN_OPEN) {
			++parens;
		} else if (t->type == TOK_PAREN_CLOSE) {
			--parens;
		} else {
			is_paren = 0;
		}

		if (parens == 0) {
			/* Done! */
			break;
		} else {	
			if (argidx > (int)N_ATTRARGS) {
				if (!error) {
					warningfl(start, "Too many arguments "
					"for attribute `%s' - ignoring ",
						attrname);
					error = 1;
				}
			} else if (!error) {
				if (!is_paren) {
					attrargs[argidx++] = t;
				}
			}
			if (t->next != NULL
				&& t->next->type == TOK_OPERATOR
				&& *(int *)t->next->data == TOK_OP_COMMA) {
				t = t->next;
			}
		}
	}
	if (error) {
		return NULL;
	}

skipargs:	
	/*
	 * Now we have an attribute declaration complete with arguments. Let's
	 * check if it looks valid
	 */
	if (at->nargs != -1 && at->nargs != argidx) {
		if (!(at->warned & 1) && at->type != A_IGNORED) {
			warningfl(t, "Attribute `%s' used with wrong number of "
				"arguments (%d instead of %d)",
				attrname, argidx, at->nargs);
			at->warned |= 1;
		}
		*tok = t;
		return NULL;
	} else if (at->type == A_UNIMPL) {
		if (!(at->warned & 2)) {
			warningfl(t, "Attribute `%s' is not implemented - this "
				"code may not work correctly without it",
				attrname);
			at->warned |= 2;
		}
		*tok = t;
		return NULL;
	} else if (at->type == A_IGNORED) {
		/* Safe to ignore */
		*tok = t;
		return NULL;
	}

	/*
	 * Check if the arguments have the correct type
	 */
	arginfo = at->argdata;
	for (i = 0; i < argidx; ++i) {
		int	is_num = 0;

		if (attrargs[i]->type != arginfo[i].type) {
			if (IS_CONSTANT(attrargs[i]->type)
				&& attrargs[i]->type !=
				TOK_STRING_LITERAL) {
				/*
				 * This may be some sort of wicked non-int
				 * type (e.g. unsigned long) while the
				 * expected type is ``int''
				 */
				if (!IS_FLOATING(attrargs[i]->type)
					&& arginfo[i].type == TY_INT) {
					is_num = 1;
				} else {
					error = 1;
				}
			} else {
				error = 1;
			}
			if (error) {
				warningfl(start, "Argument %d for "
					"attribute %s has wrong type",
					i+1, attrname);
				return NULL;
			}
		} else if (attrargs[i]->type == TY_INT) {
			is_num = 1;
		}

		if (is_num) {
			static struct tyval	tv;
			size_t			val;

			tv.type = make_basic_type(attrargs[i]->type);
			tv.value = attrargs[i]->data;
			val = cross_to_host_size_t(&tv);
			intargs[i] = val;
		} else if (attrargs[i]->type == TOK_IDENTIFIER) {
			if (arginfo->identifiers != NULL) {
				/*
				 * This identifier must correspond to some
				 * expected string
				 */
				struct name_val_pair	*tmpnv;
				char			*p = attrargs[i]->data;
				int			len;

				if (p[0] == '_' && p[1] == '_') { 
					p = (char *)attrargs[i]->data + 2;
				}
				len = strlen(p);
				if (p[len - 1] == '_' && len > 1
					&& p[len - 2] == '_') {	
					len -= 2;
				}

				for (tmpnv = arginfo->identifiers;
					tmpnv->name != NULL;
					++tmpnv) {
					if (memcmp(p, tmpnv->name, len) == 0) {
						intargs[i]= tmpnv->value;
						break;
					}
				}
				if (tmpnv->name == NULL) {
					warningfl(start, "Argument %d for "
						"attribute %s not recognized",
						i+1, attrname);
					return NULL;
				}
			}
		}
	}

	ret = alloc_attrib();
	ret->tok = start;
	ret->fastattrflag = at->fastattrflag;
	switch (ret->code = at->value) {
	case ATTRV_MODE:
		ret->iarg = intargs[0];
		break;
	case ATTRS_TRANSPARENT_UNION:
		break;
	case ATTRS_ALIGNED:
		ret->iarg = intargs[0];
		break;
	}
	
	*tok = t;
	return ret;
}

void
merge_attr_with_type(struct type *ty) {
	struct attrib	*last_node = NULL;
	struct attrib	*attr;

	for (attr = ty->attributes; attr != NULL;) {
		int	del = 0;

		switch (attr->code) {
		case ATTRS_TRANSPARENT_UNION:
			if (ty->code != TY_UNION
				|| ty->tlist != NULL) {
				errorfl(attr->tok, "Attribute "
					"`transparent_union' applied "
					"to non-union type");
				del = 1;
			} else {
				/*
				 * Check whether all members of the
				 * union have the same representation
				 */
				struct sym_entry	*se;
				struct decl		*first;

				se = ty->tstruc->scope->slist;
				first = se->dec;

				for (; se != NULL; se = se->next) {
					if (!backend->
				same_representation(first->dtype,
					se->dec->dtype)) {
						errorfl(attr->tok,
						"Not all members in "
						"`transparent_union'-"
						"attributed union have the "
						"same representation");
						del = 1;
					}
				}
			}
			break;
		case ATTRV_MODE:
			if (attr->iarg == ATTR_MODE_DI) {
				if (ty->code == TY_FLOAT) {
					ty->code = TY_DOUBLE;
				} else if (is_integral_type(ty)) {
					ty->code = TY_LLONG;
				}
			}
			break;
		case ATTRS_ALIGNED:
			if ((attr->iarg & (attr->iarg - 1)) != 0) {
				warningfl(attr->tok, "Alignment is not "
					"power of two - ignoring attribute");
				del = 1;
			}
			break;
		default:
			del = 1;
		}
		if (del) {
			/*
			 * Attribute makes no sense, delete it
			 */
			struct attrib	*tmp = attr->next;

			if (last_node == NULL) {
				/* Must be head */
				ty->attributes = ty->attributes->next;
			} else {
				last_node->next = attr->next;
			}
			free(attr);
			attr = tmp;
		} else {
			ty->fastattr |= attr->fastattrflag;
			last_node = attr;
			attr = attr->next;
		}
	}
}

struct attrib *
lookup_attr(struct attrib *attr, int type) {
	for (; attr != NULL; attr = attr->next) {
		if (attr->code == type) {
			return attr;
		}
	}
	return NULL;
}

struct attrib *
dup_attr_list(struct attrib *attr) {
	struct attrib	*ret = NULL;
	struct attrib	*ret_tail = NULL;

	for (; attr != NULL; attr = attr->next) {
		struct attrib	*tmp = n_xmemdup(attr, sizeof *attr);

		if (ret == NULL) {
			ret = ret_tail = tmp;
		} else {
			ret_tail->next = tmp;
			ret_tail = tmp;
		}
	}
	return ret;
}

struct attrib *
get_attribute(struct type *ty, struct token **tok) {
	struct token		*t = *tok;
	struct attrib		*ret = NULL;
	int			err;

#if 0
	/* XXX temporary */
	*tok = ignore_attr(*tok);
	return NULL;
#endif

	/* 
	 * Function might be called at ``__attribute__'' so as to avoid
	 * cluttering the caller with next_token() calls
	 */
	if (t->type == TOK_KEY_ATTRIBUTE) {
		if (next_token(&t) != 0) {
			return NULL;
		}
	}

	/* Every attribute must look like ``__attribute__((attr-list))'' */
	if (t->type != TOK_PAREN_OPEN) {
		errorfl(t, "Syntax error at %s", t->ascii);
		*tok = t;
		return NULL;
	}

	if (next_token(&t) != 0) {
		*tok = t;
		return NULL;
	}

	if (t->type != TOK_PAREN_OPEN) {
		errorfl(t, "Syntax error at %s", t->ascii);
		*tok = t;
		return NULL;
	}

#if 0
	ret = n_xmalloc(sizeof *ret);
	*ret = nullattr;
#endif

	if (next_token(&t) != 0) {
		*tok = t;
		return NULL;
	}
	err = 0;
	for (; t != NULL; t = t->next) {
		if (t->type == TOK_PAREN_CLOSE) {
			/* Ends here */
			if (next_token(&t) != 0) {
				*tok = t;
				free_attrib(ret);
				return NULL;
			}

			/* ... )) */
			if (t->type != TOK_PAREN_CLOSE) {
				errorfl(t, "Syntax error at %s", t->ascii);
				*tok = t;
				free_attrib(ret);
				return NULL;
			}

			if (err) {
				return NULL;
			} else {
				*tok = t->next;
				return ret;
			}
		} else {
			/* This must be another attribute */
			struct attrib	*tmp;
			int		fatal;

			tmp = get_single_attribute(&t, &fatal);
			if (tmp == NULL && fatal) {
				*tok = ignore_attr(*tok);
				return NULL;
			} else if (tmp != NULL) {
				if (ty->attributes == NULL) {
					ty->attributes = ty->attributes_tail =
						tmp;
				} else {
					ty->attributes_tail->next = tmp;
					ty->attributes_tail = tmp;
				}
				if (ret == NULL) {
					ret = tmp;
				}
			}

			if (t->next
				&& t->next->type == TOK_OPERATOR
				&& *(int *)t->next->data == TOK_OP_COMMA) {
				t = t->next;
			}
		}
	}
	errorfl(*tok, "Unexpected end of file");
	*tok = t;
	return NULL;
}


struct token *
ignore_attr(struct token *tok) {
	int	parens = 0;

	if (tok == NULL) {
		return NULL;
	}
	if (tok->type != TOK_KEY_ATTRIBUTE) {
		return tok;
	}
	for (tok = tok->next; tok != NULL; tok = tok->next) {
		if (tok->type == TOK_PAREN_OPEN) {
			++parens;
		} else if (tok->type == TOK_PAREN_CLOSE) {
			if (--parens == 0) {
				return tok->next;
			}
		}
	}
	return NULL;
}

void
stupid_append_attr(struct attrib **dest, struct attrib *src) {
	struct attrib	*d = *dest;

	while (d && d->next) {
		d = d->next;
	}
	if (d) {
		d->next = src;
	} else {
		*dest = src;
	}	
}



syntax highlighted by Code2HTML, v. 0.9.1