/* $Id: scope.c,v 1.68 2012/02/17 14:17:00 vrsieh Exp $ 
 *
 * Copyright (C) 2007-2009 FAUcc Team <info@faumachine.org>.
 * This program is free software. You can redistribute it and/or modify it
 * under the terms of the GNU General Public License, either version 2 of
 * the License, or (at your option) any later version. See COPYING.
 */

#include <assert.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include "setup.h"
#include "declaration.h"
#include "stmt.h"
#include "scope.h"
#include "cc1.h"

static struct declaration *dion_current;
struct scope *scope_current;


static int
_scope_lookup_enum(
        struct scope *scope,
        const char *name,
        struct declaration **dorp
)
{
	struct declaration *dion;

	assert(scope->type == SCOPE_ENUM);

	for (dion = scope->declaration_first; ; dion = dion->next) {
		if (! dion) {
			return -1;
		}
		if (declaration_name_get(dion)
		 && strcmp(declaration_name_get(dion), name) == 0) {
			*dorp = dion;
			return TYPE_ENUM;
		}
	}
}

static int
_scope_lookup(
        struct scope *scope,
        const char *name,
        struct declaration **dorp
)
{
        struct declaration *dion;

        for (dion = scope->declaration_first; ; dion = dion->next) {
                if (! dion) {
                        /* Not Found */
                        return -1;
                }
		if (dion->type_name
		 && dion->type_name->type == TYPE_ENUM
		 && dion->type_name->scope) {
			int ret;

			ret = _scope_lookup_enum(
					dion->type_name->scope, name,
					dorp);
			if (0 <= ret) {
				return ret;
			}
		}
		if (declaration_name_get(dion)
		 && strcmp(declaration_name_get(dion), name) == 0) {
			*dorp = dion;
			if (scope->type == SCOPE_ENUM) {
				return TYPE_ENUM;
			} else {
				return dion->storage;
			}
		}
        }
}

int
_scope_lookup_structunionenum(
	struct scope *scope,
	enum type_type type,
	const char *name,
	struct type **tsp
)
{
	struct declaration *dion;

	assert(type == TYPE_STRUCT
	    || type == TYPE_UNION);

        for (dion = scope->declaration_first; ; dion = dion->next) {
                if (! dion) {
                        /* Not Found */
                        return -1;
                }
		if (dion->type_name
		 && dion->type_name->type == type
		 && dion->type_name->identifier
		 && strcmp(dion->type_name->identifier, name) == 0
		 && dion->type_name->scope) {
			*tsp = dion->type_name;
			return 0;
		}
	}
}

static void
_scope_add(struct declaration *d)
{
	d->prev = scope_current->declaration_last;
	d->next = NULL;
	if (d->prev) {
		d->prev->next = d;
	} else {
		scope_current->declaration_first = d;
	}
	scope_current->declaration_last = d;
}

static void
_scope_enter(void)
{
	struct scope *s;

	s = malloc(sizeof(*s));
	assert(s);

	memset(s, 0, sizeof(*s));

	s->prev = scope_current->child_last;
	s->next = NULL;
	if (s->prev) {
		s->prev->next = s;
	} else {
		scope_current->child_first = s;
	}
	scope_current->child_last = s;

	s->parent = scope_current;

	scope_current = s;
}

static void
_scope_leave(void)
{
	scope_current = scope_current->parent;
}

int
scope_lookup_one(
	struct scope *scope,
	const char *name,
	struct declaration **dorp
)
{
	assert(scope);

	return _scope_lookup(scope, name, dorp);
}

int
scope_lookup(
	struct scope *scope,
	const char *name,
	struct declaration **dorp
)
{
        struct scope *s;
        int ret;

	s = scope;

again:	;
	ret = _scope_lookup(s, name, dorp);
	if (0 <= ret) {
		return ret;
	}

	switch (s->type) {
	case SCOPE_NONE:
		assert(0);

	case SCOPE_GLOBAL:
		/*
		 * Nothing more to search...
		 */
		return -1;

	case SCOPE_FUNCTION:
		/*
		 * Next step: look in parameter list.
		 */
		assert(s->function);

		s = type_parameter_get(s->function->type_name);
		assert(s);
		goto again;

	case SCOPE_PARAMETER:
		/*
		 * Next step: look in parent list.
		 */
		s = s->parent;
		goto again;

	case SCOPE_BLOCK:
		/*
		 * Next step: look in upper block / function.
		 */
		s = s->parent;
		assert(s->type == SCOPE_BLOCK
		    || s->type == SCOPE_FUNCTION);
		goto again;

	case SCOPE_STRUCT:
	case SCOPE_UNION:
	case SCOPE_ENUM:
		/*
		 * Next step: look in upper block / function / global list.
		 */
		s = s->parent;
		goto again;

	default:
		assert(0);
	}
}

int
scope_lookup_current(
	const char *name,
	struct declaration **dorp
)
{
	return scope_lookup(scope_current, name, dorp);
}

int
scope_lookup_structunionenum(
	struct scope *scope,
	enum type_type type,
	const char *name,
	struct type **tsp
)
{
	struct scope *s;
	int ret;

	assert(type == TYPE_STRUCT
	    || type == TYPE_UNION);

	for (s = scope; ; s = s->parent) {
		if (! s) {
			return -1;
		}
		ret = _scope_lookup_structunionenum(s, type, name, tsp);
		if (0 <= ret) {
			return ret;
		}
	}
}

static void
_scope_structunionenum_begin(
	const char *identifier,
	enum type_type ttype,
	enum scope_type stype
)
{
	struct declaration *dion;

	if (dion_current) {
		_scope_leave();
	}

	dion = declaration_identifier(NULL);
	dion->type_name = type_new();
	dion->type_name->type = ttype;
	dion->type_name->identifier = identifier;

	if (dion_current) {
		/* Prepend to current struct definition. */
		dion->prev = dion_current->prev;
		dion->next = dion_current;
	} else {
		/* Append to declaration list. */
		dion->prev = scope_current->declaration_last;
		dion->next = NULL;
	}
	if (dion->prev) {
		dion->prev->next = dion;
	} else {
		scope_current->declaration_first = dion;
	}
	if (dion->next) {
		dion->next->prev = dion;
	} else {
		scope_current->declaration_last = dion;
	}

	dion_current = dion;
	_scope_enter();
	scope_current->type = stype;

	dion_current->type_name->scope = scope_current;
}

static void
_scope_structunionenum_end(void)
{
	_scope_leave();
	dion_current = dion_current->next;

	if (dion_current) {
		scope_current = dion_current->type_name->scope;
	}
}

void
scope_struct_begin(const char *identifier)
{
	_scope_structunionenum_begin(identifier, TYPE_STRUCT, SCOPE_STRUCT);
}

void
scope_struct_end(void)
{
	_scope_structunionenum_end();
}

void
scope_union_begin(const char *identifier)
{
	_scope_structunionenum_begin(identifier, TYPE_UNION, SCOPE_UNION);
}

void
scope_union_end(void)
{
	_scope_structunionenum_end();
}

void
scope_enum_begin(const char *identifier)
{
	_scope_structunionenum_begin(identifier, TYPE_ENUM, SCOPE_ENUM);
}

void
scope_enum_add(const char *identifier, unsigned int val)
{
	struct declaration *dion;

	dion = declaration_identifier(identifier);
	dion->type_name = type_int();
	declaration_initializer_set(dion, expr_integer(val));

	dion->prev = scope_current->declaration_last;
	dion->next = NULL;
	if (dion->prev) {
		dion->prev->next = dion;
	} else {
		scope_current->declaration_first = dion;
	}
	scope_current->declaration_last = dion;
}

void
scope_enum_end(void)
{
	_scope_structunionenum_end();
}

void
scope_parameter_begin(void)
{
	_scope_enter();

	scope_current->type = SCOPE_PARAMETER;
}

void
scope_parameter_end(
	struct declaration *first,
	struct declaration *last
)
{
	scope_current->declaration_first = first;
	scope_current->declaration_last = last;

	_scope_leave();
}

void
scope_block_begin(void)
{
	_scope_enter();

	scope_current->type = SCOPE_BLOCK;
}

void
scope_block_end(struct stmt *s)
{
	s->scope = scope_current;

	_scope_leave();
}

void
scope_function_begin(struct type *ts, struct declaration *dion)
{
	assert(scope_current->parent == NULL);

	dion->type_name = type_add_user(dion->type_name, ts);

	_scope_add(dion);

	_scope_enter();

	scope_current->type = SCOPE_FUNCTION;
	scope_current->function = dion;
}

struct label *
scope_function_label_get(const char *identifier)
{
	struct scope *s;
	struct label *l;

	for (s = scope_current; s->type == SCOPE_BLOCK; s = s->parent) {
	}

	for (l = s->label_first; ; l = l->next) {
		if (! l) {
			l = label_new(identifier);

			l->prev = s->label_last;
			l->next = NULL;
			if (l->prev) {
				l->prev->next = l;
			} else {
				s->label_first = l;
			}
			s->label_last = l;

			return l;
		}
		if (strcmp(l->identifier, identifier) == 0) {
			return l;
		}
	}
}

void
scope_function_end(struct stmt *s)
{
	scope_block_end(s);

	assert(scope_current->parent == NULL);

	scope_current->declaration_last->stmt = s;
}

void
scope_declaration_prepend_first(
	struct scope *scope,
	struct declaration *nd
)
{
	nd->prev = NULL;
	nd->next = scope->declaration_first;

	scope->declaration_first = nd;
	if (nd->next) {
		nd->next->prev = nd;
	} else {
		scope->declaration_last = nd;
	}
}

void
scope_declaration_prepend(
	struct scope *scope,
	struct declaration *od,
	struct declaration *nd
)
{
	assert(od);

	nd->prev = od->prev;
	nd->next = od;
	if (nd->prev) {
		nd->prev->next = nd;
	} else {
		scope->declaration_first = nd;
	}
	nd->next->prev = nd;
}

void
scope_declaration_append(struct scope *scope, struct declaration *dion)
{
	/*
	 * Fix definitions of int8_t, uint8_t, ..., uint64_t.
	 */
	if (dion->storage == STORAGE_TYPEDEF
	 && dion->identifier) {
		struct {
			const char *name;
			struct type *(*func)(void);
		} table[] = {
			{ "int8_t", type_int8 },
			{ "uint8_t", type_uint8 },
			{ "int16_t", type_int16 },
			{ "uint16_t", type_uint16 },
			{ "int32_t", type_int32 },
			{ "uint32_t", type_uint32 },
			{ "int64_t", type_int64 },
			{ "uint64_t", type_uint64 },

			{ "int_least8_t", type_int8 },
			{ "uint_least8_t", type_uint8 },
			{ "int_least16_t", type_int16 },
			{ "uint_least16_t", type_uint16 },
			{ "int_least32_t", type_int32 },
			{ "uint_least32_t", type_uint32 },
			{ "int_least64_t", type_int64 },
			{ "uint_least64_t", type_uint64 },

			{ "intmax_t", type_int64 },
			{ "uintmax_t", type_uint64 },

			{ "intptr_t", type_ptrdiff },
			{ "uintptr_t", type_uintptr_t },
		};
		unsigned int i;

		for (i = 0; i < sizeof(table) / sizeof(table[0]); i++) {
			if (strcmp(dion->identifier, table[i].name) == 0
			 && dion->type_name != (*table[i].func)()) {
				fprintf(stderr, "%s: Fixing declaration of %s.\n",
						progname, table[i].name);
				dion->type_name = (*table[i].func)();
			}
		}
	}

	/*
	 * Append to declaration list.
	 */
	dion->prev = scope->declaration_last;
	dion->next = NULL;
	if (dion->prev) {
		dion->prev->next = dion;
	} else {
		scope->declaration_first = dion;
	}
	scope->declaration_last = dion;
}

void
scope_asm_add(unsigned int len, const char *code)
{
	struct declaration *dion;

	dion = declaration_new();
	dion->storage = STORAGE_ASM;
	dion->code = code;

	scope_declaration_append(scope_current, dion);
}

void
scope_file_begin(void)
{
	scope_current = malloc(sizeof(*scope_current));
	assert(scope_current);

	memset(scope_current, 0, sizeof(*scope_current));

	scope_current->type = SCOPE_GLOBAL;
}

void
scope_file_end(struct scope **sp)
{
	assert(scope_current->parent == NULL);

	*sp = scope_current;
}
