/* Top-level object parsing.
   Copyright (C) 2001, 2002 Michal Moskal (malekith@pld-linux.org)

This file is part of GCC.

GCC is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2, or (at your option)
any later version.

GCC is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with GCC; see the file COPYING.  If not, write to
the Free Software Foundation, 59 Temple Place - Suite 330,
Boston, MA 02111-1307, USA.  */


#include "ksi.h"
#include "ggc.h"	/* ggc_collect() */
#include "rtl.h"	/* for expr.h */
#include "expr.h"	/* new_alias_set() */

static void parse_func_args(tree *param_list, tree *param_type_list)
{
	struct list_state ctx;
	keyword_t kw;
	tree type, name, arg;
	int halt = 0;

	expect_list(&ctx);

	while (!halt) {
		kw = get_keyword_from_list(&ctx);
	
		switch (kw) {
		case kw__error:
			*param_type_list = tree_cons(NULL_TREE, void_type_node, 
						     *param_type_list);
			halt++;
			break;
		case kw_ellipsis:
			arg0_term(kw);
			if ((kw = get_keyword_from_list(&ctx)) != kw__error)
				fatal_error("found (%s ...) after (ellipsis) "
					    "in parm list", keyword_name(kw));
			halt++;
			break;
		case kw_var:
			parse_var(&type, &name);
			arg = build_decl(PARM_DECL, name, type);
			DECL_ARG_TYPE(arg) = type;
			*param_list = chainon(arg, *param_list);
			*param_type_list = tree_cons(NULL_TREE, type, 
						     *param_type_list);
			break;
		default:
			fatal_error("found (%s ...) where (var ...) or (nil) "
				    "was expected", keyword_name(kw));
			break;	
		}
	}

	*param_list = nreverse(*param_list);
	*param_type_list = nreverse(*param_type_list);
}

extern tree dbx_last_input_file;
extern int dbx_last_line, dbx_last_col;

static void compile_body(tree fndecl, keyword_t kw)
{
	tree param_decl, next_param;
	tree body, dummy;

	/* Set line number information.  */
	DECL_SOURCE_FILE(fndecl) = IDENTIFIER_POINTER(dbx_last_input_file);
	DECL_SOURCE_LINE(fndecl) = dbx_last_line;

	/* Announce we are compiling this function.  */
	announce_function(fndecl);

	/* Set up to compile the function and enter it.  */
	current_function_decl = fndecl;
	DECL_INITIAL(fndecl) = error_mark_node;

	pushlevel(0);
	make_decl_rtl(fndecl, 0);

	/* Push all the PARM_DECL nodes onto the current scope (i.e. the 
	 * scope of the subprogram body) so that they can be recognized 
	 * as local variables in the subprogram.   */

	for (param_decl = nreverse(DECL_ARGUMENTS(fndecl));
	     param_decl; param_decl = next_param) {
		next_param = TREE_CHAIN(param_decl);
		TREE_CHAIN(param_decl) = NULL;
		pushdecl(param_decl);
	}

	/* Store back the PARM_DECL nodes. They appear in the right order. */
	DECL_ARGUMENTS(fndecl) = getdecls();

	init_function_start(fndecl, IDENTIFIER_POINTER(dbx_last_input_file), 0);
	expand_function_start(fndecl, 0);
	expand_start_bindings(0);

	/* The only thing in this function is a single return statement, which
	   we now generate.  Note that EXP will have been allocated in
	   permanent_obstack.  To do this properly, we need to enter the 
	   function context before parsing the expression, but then error 
	   recovery is more complex, so we don't do that here.  */

	/* Hm... I wonder if it really *has* to be (bind ...),
	 * or I do sth wrong. If it really does, we can wrap
	 * function in it automagicly. */

	if (kw != kw_bind)
		fatal_error("function body top-expressoion isn't "
			    "(bind ...), but (%s ...)", 
			    keyword_name(kw));
			    
	body = compile(kw, &dummy);

	fini_labels();
	
	if (flag_yydebug)
		debug_tree(body);

	expand_expr_stmt(body);

	/* Now get back out of the function and compile it.  */
	expand_end_bindings(NULL_TREE, 1, 0);

	poplevel(1, 0, 1);

	BLOCK_SUPERCONTEXT(DECL_INITIAL(fndecl)) = fndecl;

	/* Generate rtl for function exit.  */
	expand_function_end(IDENTIFIER_POINTER(dbx_last_input_file), 
			    dbx_last_line, 0);

	/* Run the optimizers and output assembler code for this function. */
	rest_of_compilation(fndecl);

	/* Garbage collect */
	ggc_collect();

	current_function_decl = NULL_TREE;
}

/* func(local|public|extern, RET_TYPE, NAME, ARGS, BODY) */
void parse_func(void)
{
	struct term_state ctx;
	struct attr_info attr;
	keyword_t kw;
	tree ret_type;
	tree name;
	tree fndecl = NULL, fntype;
	tree param_list = NULL_TREE;
	tree param_type_list = NULL_TREE;
	int had_decl = 0;
	
	init_term(&ctx, kw_func);

	kw = maybe_attr_then_keyword(&ctx, &attr, ak_function, kw__error);
	
	ret_type = parse_type(kw);
	
	name = get_id(&ctx);
	
	fndecl = lookup_id(IDENTIFIER_POINTER(name), FUNCTION_DECL);
	
	parse_func_args(&param_list, &param_type_list);

	/* The function type depends on the return type and type of args.  */
	fntype = build_function_type(ret_type, param_type_list);

	if (fndecl) {
		/* check if it agrees */
		same_types(kw_func, TREE_TYPE(fndecl), fntype);
		had_decl++;
	}

	/* first keyword of the body */
	kw = get_keyword(&ctx);
	
	/* Now make the function decl.  */
	if (fndecl == NULL)
		fndecl = build_decl(FUNCTION_DECL, name, fntype);
		
	if (had_decl && kw != kw_nil && !TREE_STATIC(fndecl))
		error("redefinition of function `%s'", 
		      IDENTIFIER_POINTER(name));
	TREE_STATIC(fndecl) = kw == kw_nil;

	if (attr.external)
		attr.public = 1;

	if (attr.external && kw != kw_nil)
		fatal_error("extern function cannot have body");
		
#define X(MACRO, fld, aname) \
	if (had_decl && attr.fld && !MACRO(fndecl))  \
		error("function `%s' now defined with `%s', but without it " \
		      "before", IDENTIFIER_POINTER(name), aname); \
	if (had_decl && !attr.fld && MACRO(fndecl)) \
		error("function `%s' now defined without `%s', but with it " \
		      "before", IDENTIFIER_POINTER(name), aname); \
	MACRO(fndecl) = attr.fld

	/* Allow redefinition with (extern) after (public) and vice-versa. */
	if (had_decl) {
		if (!attr.public && DECL_EXTERNAL(fndecl))
			error("function `%s' declared (extern) and now defined"
			      "(local)", IDENTIFIER_POINTER(name));
		if (attr.external && !TREE_PUBLIC(fndecl))
			error("function `%s' defined (local) and now declared"
			      "(extern)", IDENTIFIER_POINTER(name));
	}

	if (!had_decl || DECL_EXTERNAL(fndecl))
		DECL_EXTERNAL(fndecl) = attr.external;
	
	X(TREE_PUBLIC, public, "public");
	X(TREE_THIS_VOLATILE, no_return, "no_return");
	X(DECL_IS_MALLOC, malloc_like, "malloc_like");
	X(TREE_READONLY, const_func, "const_func");
	X(DECL_IS_PURE, pure_func, "pure_func");
	X(DECL_STATIC_CONSTRUCTOR, static_ctor, "static_ctor");
	X(DECL_STATIC_DESTRUCTOR, static_dtor, "static_dtor");
	if (attr.static_ctor || attr.static_ctor)
		TREE_USED(fndecl) = 1;
	set_align(fndecl, &attr);	
	X(DECL_IGNORED_P, suppress_debug, "suppress_debug");
#if 0
	/* no longer present in gcc 3.1 */
	X(DECL_NO_CHECK_MEMORY_USAGE, no_check_memory_usage, 
	  "no_check_memory_usage");
#endif
	X(DECL_NO_LIMIT_STACK, no_limit_stack, "no_limit_stack");
	/* TODO: weak, one_only, comdat, artificial, section */
#undef X

	DECL_ARGUMENTS(fndecl) = param_list;

	if (!had_decl) {
		DECL_RESULT(fndecl) = build_decl(RESULT_DECL, NULL_TREE, 
						 ret_type);
		DECL_CONTEXT(DECL_RESULT(fndecl)) = fndecl;
	}

	if (!had_decl)
		pushdecl(fndecl);
	
	if (attr.export_name)
		DECL_NAME(fndecl) = attr.export_name;
	
	rest_of_decl_compilation(fndecl, NULL, 1, 0);

	if (kw == kw_nil)
		arg0_term(kw);
	else
		compile_body(fndecl, kw);
		
	fini_term(&ctx);
}

void set_align(tree decl, struct attr_info *attr)
{
	int i, align;

	if (attr->align == 0)
		return;		/* nothing to do */
		
	i = exact_log2(attr->align);
	if (i == -1)
		fatal_error("requested alignment is not a power of 2");
	else if (i > HOST_BITS_PER_INT - 2)
		fatal_error("requested alignment is too large");

	align = (1 << i) * BITS_PER_UNIT;
	
	if (TYPE_P(decl)) {
		TYPE_ALIGN(decl) = align;
		TYPE_USER_ALIGN(decl) = 1;
	} else {
		DECL_ALIGN(decl) = align;
		DECL_USER_ALIGN(decl) = 1;
	}
}

void parse_global()
{
	struct term_state ctx;
	struct attr_info attr;
	tree type, name, decl;
	
	init_term(&ctx, kw_global);

	maybe_attr_then_keyword(&ctx, &attr, ak_global_variable, kw_var);

	parse_var(&type, &name);
	fini_term(&ctx);

	decl = build_decl(VAR_DECL, name, type);
	
	DECL_EXTERNAL(decl) = attr.external;
	TREE_PUBLIC(decl) = attr.public;
	TREE_STATIC(decl) = !attr.external;
	DECL_IGNORED_P(decl) = attr.suppress_debug;
	DECL_WEAK(decl) = attr.weak;
	if (!attr.public && (attr.one_only || attr.comdat))
		error("Used attr one_only | comdat in non_public");
	DECL_ONE_ONLY(decl) = attr.one_only;
	DECL_COMDAT(decl) = attr.comdat;
	DECL_ARTIFICIAL(decl) = attr.artificial;
	set_align(decl, &attr);
#ifdef ASM_OUTPUT_SECTION_NAME
	if (attr.section_name)
		DECL_SECTION_NAME(decl) = attr.section_name;
#else
	if (attr.section_name)
		error("section attributes are not supported for this target");
#endif
	DECL_CONTEXT(decl) = NULL;
	
	pushdecl(decl);

	if (attr.export_name)
		DECL_NAME(decl) = attr.export_name;
	
	rest_of_decl_compilation(decl, NULL, 1, 0);
}

void parse_unit()
{
	struct list_state ctx;
	keyword_t kw;

	expect_list(&ctx);
	
	while ((kw = get_keyword_from_list(&ctx)) != kw__error) {
		switch (kw) {
		case kw_func:
			parse_func();
			break;
		case kw_global:
			parse_global();
			break;
		case kw_uniondef:
		case kw_structdef:
			parse_structdef(kw == kw_uniondef);
			break;
		case kw_input_file:
			parse_input_file();
			break;
		case kw_alias_subset:
			parse_alias_subset();
			break;
		case kw_tyalias:
			parse_tyalias();
			break;
		case kw_abstract_type:
			parse_abstract_type();
			break;
		case kw_enumdef:
			parse_enumdef();
			break;
		default:
			fatal_error("(%s ...) cannot be part of unit", 
				    keyword_name(kw));
			break;
		}
	}
}

/* var(type(uint, 32), x) */
void parse_var(tree *type, tree *name)
{
	struct term_state ctx;

	init_term(&ctx, kw_var);
	*type = parse_type(get_keyword(&ctx));
	*name = get_id(&ctx);
	fini_term(&ctx);
	
	if (TYPE_SIZE(*type) == 0)
		fatal_error("trying to use incomplete type");
}

/* (structdef [ATTR] NAME FIELDS) */
void parse_structdef(int uniondef)
{
	struct term_state ctx;
	struct arg a;
	struct list_state ltx;
	struct attr_info attr;
	tree record, record_name;
	tree decl;
	tree last_field = NULL;

	init_term(&ctx, uniondef ? kw_uniondef : kw_structdef);

	maybe_attr(&ctx, &attr, ak_type, arg_id, &a);
	
	record_name = get_identifier(a.u.val_id);

	decl = lookup_type(record_name, uniondef ? UNION_TYPE : RECORD_TYPE);
	
	if (decl == NULL) {
		/* push it here as we can reference it from structure
		 * definition */
		record = make_node(uniondef ? UNION_TYPE : RECORD_TYPE);
		decl = build_decl(TYPE_DECL, record_name, record);
		pushdecl(decl);
	} else {
		record = TREE_TYPE(decl);
		
		if (TYPE_SIZE(record)) {
			fatal_error("redefinition of %s `%s'", 
			      	    uniondef ? "union" : "struct",
				    IDENTIFIER_POINTER(record_name));
		}
	}

	DECL_TAGGED_TYPE(decl) = 1;
	
	if (attr.export_name)
		DECL_NAME(decl) = attr.export_name;
	
	expect_list(&ltx);
	
	for (;;) {
		keyword_t kw;
		tree type, name, field;
		
		kw = get_keyword_from_list(&ltx);
		if (kw == kw__error)
			break;
		if (kw != kw_var)
			fatal_error("found (%s ...) in field list, "
				    "instead of expected (var ...)", 
				    keyword_name(kw));
		parse_var(&type, &name);
		field = build_decl(FIELD_DECL, name, type);
		DECL_CONTEXT(field) = record;
		if (last_field)
			TREE_CHAIN(last_field) = field;
		else
			TYPE_FIELDS(record) = field;
		last_field = field;
	}

	fini_term(&ctx);

	TYPE_STUB_DECL(record) = decl;

	if (attr.align) {
		set_align(decl, &attr);
		TYPE_ALIGN(record) = attr.align * BITS_PER_UNIT;
		TYPE_USER_ALIGN(record) = 1;
	}
	
	if (attr.packed)
		TYPE_PACKED(record) = 1;
	if (attr.artificial)
		DECL_ARTIFICIAL(decl) = 1;
	if (attr.suppress_debug) {
		TYPE_DECL_SUPPRESS_DEBUG(decl) = 1;
		DECL_IGNORED_P(decl) = 1;
	}
		
	if (TYPE_FIELDS(record) == NULL) {
		/* forward declaration */
		return;
	}
	
	layout_type(record);
	rest_of_type_compilation(record, global_bindings_p());
}

HOST_WIDE_INT get_id_alias_set(tree id)
{
	HOST_WIDE_INT set;

	if (!flag_strict_aliasing)
		return 0;

	set = IDENTIFIER_ALIAS_SET(id);
	
	if (set == 0)
		set = IDENTIFIER_ALIAS_SET(id) = new_alias_set();
	
	return set;
}

/* (alias_subset PARENT CHILD) */
void parse_alias_subset(void)
{
	struct term_state ctx;
	tree parent, child;
	
	init_term(&ctx, kw_alias_set);
	parent = get_id(&ctx);
	child = get_id(&ctx);
	fini_term(&ctx);

	if (!flag_strict_aliasing)
		return;

	record_alias_subset(get_id_alias_set(parent), get_id_alias_set(child));
}

static void xoops(keyword_t kw, enum attr_kind ak)
{
	const char *name = NULL;
	
	switch (ak) {
	case ak_function:
		name = "function";
		break;
	case ak_global_variable:
		name = "global variable";
		break;
	case ak_local_variable:
		name = "local variable";
		break;
	case ak_variable:
		name = "variable";
		break;
	case ak_type:
		name = "type";
		break;
	default:
		ice();
	}

	fatal_error("attribute (%s ...) not allowed for %s",
		    keyword_name(kw), name);
}

static int get_align(void)
{
	struct term_state ctx;
	struct arg a;
	
	init_term(&ctx, kw_aligned);
	get_term_arg(&ctx, arg_int|arg_uint, &a);
	fini_term(&ctx);

	return a.u.val_int.low;
}

static tree get_section(void)
{
	struct term_state ctx;
	struct arg a;
	
	init_term(&ctx, kw_aligned);
	get_term_arg(&ctx, arg_string, &a);
	fini_term(&ctx);

	return make_string(a.u.val_string.len, a.u.val_string.data);
}

static tree parse_export_name(void)
{
	struct term_state ctx;
	struct arg a;
	
	init_term(&ctx, kw_export_name);
	get_term_arg(&ctx, arg_string, &a);
	fini_term(&ctx);

	return get_identifier_with_length(a.u.val_string.data, 
					  a.u.val_string.len);
}

static HOST_WIDE_INT parse_alias_set(void)
{
	struct term_state ctx;
	tree id;
	
	init_term(&ctx, kw_alias_set);
	id = get_id(&ctx);
	fini_term(&ctx);

	return get_id_alias_set(id);
}

void parse_attr(struct attr_info *ret, enum attr_kind ak)
{
	struct list_state ctx;
	keyword_t kw;

	init_list(&ctx);
	memset(ret, 0, sizeof(*ret));

#define SET_IF(field, kind) 		\
	case kw_ ## field:		\
		if (ak & (kind))	\
			ret->field = 1; \
		else			\
			xoops(kw, ak);	\
		arg0_term(kw_ ## field);\
		break
		
	while ((kw = get_keyword_from_list(&ctx)) != kw__error)
		switch (kw) {
		SET_IF(packed, ak_type);
		SET_IF(pure_func, ak_function);
		SET_IF(const_func, ak_function);
		SET_IF(suppress_debug, ak_function|ak_variable);
		SET_IF(static_ctor, ak_function);
		SET_IF(static_dtor, ak_function);
		SET_IF(malloc_like, ak_function);
		SET_IF(no_check_memory_usage, ak_function);
		SET_IF(no_limit_stack, ak_function);
		SET_IF(no_return, ak_function);
		SET_IF(weak, ak_toplev_data);
		SET_IF(one_only, ak_toplev_data);
		SET_IF(comdat, ak_toplev_data);
		SET_IF(public, ak_toplev_data);
		SET_IF(artificial, ak_whatever);
#undef SET_IF
		/* special cases: */
		case kw_extern:
			if (ak & ak_toplev_data)
				ret->external = 1;
			else
				xoops(kw, ak);
			arg0_term(kw);
			break;
		case kw_local:
			if (ak & ak_toplev_data)
				ret->public = 0;
			else
				xoops(kw, ak);
			arg0_term(kw);
			break;
		case kw_aligned:
			if (ak & ak_whatever) {
				int a = get_align();
				if (a > ret->align)
					ret->align = a;
			} else
				xoops(kw, ak);
			break;
		case kw_section:
			if (ak & ak_toplev_data)
				ret->section_name = get_section();
			else
				xoops(kw, ak);
			break;
		case kw_alias_set:
			if (ak & ak_type)
				ret->alias_set = parse_alias_set();
			else
				xoops(kw, ak);
			break;
		case kw_export_name:
			ret->export_name = parse_export_name();
			break;
		default:
			fatal_error("(%s ...) cannot be used as part of "
				    "[attr ...] list", keyword_name(kw));
		}
}

void maybe_attr(struct term_state *ctx, struct attr_info *ret, 
	        enum attr_kind ak, int allowed, struct arg *a)
{
	get_term_arg(ctx, allowed | arg_list_beg, a);

	if (a->sel == arg_list_beg) {
		parse_attr(ret, ak);
		get_term_arg(ctx, allowed, a);
	} else
		memset(ret, 0, sizeof(*ret));
}

keyword_t maybe_attr_then_keyword(struct term_state *ctx, 
				  struct attr_info *ret, 
	        	     	  enum attr_kind ak, 
				  keyword_t expected)
{
	struct arg a;

	maybe_attr(ctx, ret, ak, arg_kw, &a);
	
	if (expected != kw__error && a.u.val_kw != expected)
		fatal_error("found (%s ...) where (%s ...) was expected", 
			    keyword_name(a.u.val_kw), 
			    keyword_name(expected));

	return a.u.val_kw;
}
