/* %Z%%M% %I% %E% */

/*
 * Copyright (c) 1990-2006 by Wayne C. Gramlich.
 * All rights reserved.
 */

/*
 * This file contains code for converting from expression tress to call trees:
 */

#ifndef CALL_DEFS_H
#include "call_defs.h"
#endif

#ifndef ERROR_EXPORTS_H
#include "error_exports.h"
#endif

#ifndef EXP_DEFS_H
#include "exp_defs.h"
#endif

#ifndef HEAP_EXPORTS_H
#include "heap_exports.h"
#endif

#ifndef LIBC_EXPORTS_H
#include "libc_exports.h"
#endif

#ifndef LINT_H
#include "lint.h"
#endif

#ifndef MSG_EXPORTS_H
#include "msg_exports.h"
#endif

#ifndef OBJECT_EXPORTS_H
#include "object_exports.h"
#endif

#ifndef ROUTINE_DEFS_H
#include "routine_defs.h"
#endif

#ifndef STATEMENT_DEFS_H
#include "statement_defs.h"
#endif

#ifndef TABLE_EXPORTS_H
#include "table_exports.h"
#endif

#ifndef TYPE_DEFS_H
#include "type_defs.h"
#endif

#ifndef UNIX_ASSERT_H
#include "unix_assert.h"
#endif

#ifndef VAR_DEFS_H
#include "var_defs.h"
#endif

#ifndef VECTOR_DEFS_H
#include "vector_defs.h"
#endif

LOCAL void	statement_exp_convert(Statement, Exp, Convert);
LOCAL void	statement_convert(Statement, Convert);
LOCAL void	statement_logical_convert(Statement, Convert);

/*
 * statement_convert(statement, convert)
 *	This routine will type check "statement" using "covnert".
 */
LOCAL void
statement_convert(
	Statement	statement,
	Convert		convert)
{
	switch (statement->kind) {
	    case Statement_kind_assert:
		statement_logical_convert(statement, convert);
		break;
	    case Statement_kind_break:
		break;
	    case Statement_kind_continue:
		break;
	    case Statement_kind_eval:
		statement->calls = call_assign_convert(convert,
						       statement->value.exp);
		break;
	    case Statement_kind_extract:
	      {
		Exp			exp;
		Statement_extract	extract;
		Object_table		object_table;
		Statement_tag		tag;
		Str			tag_name;
		Vec(Str)		tag_names;
		Vec(Statement_tag)	tags;
		Type_def		type_def;
		Type_ref		type_ref;
		Type_refs		type_refs;
		Type_variant		type_variant;
		Var			var;

		statement->calls = vec_create(Call, convert->heap);
		extract = statement->value.extract;
		exp = extract->exp;
		exp->call = call_exp_convert(convert, exp,
					     convert->type_refs_empty,
					     statement->calls);

		/* Make sure that the type definition exists and is correct: */
		type_refs = exp->type_refs;
		assert(type_refs != (Type_refs)0);
		assert(type_refs_size(type_refs) == 1);
		type_ref = type_refs_fetch(type_refs, 0);
		type_def = type_def_table_lookup(convert->type_def_table,
						 type_ref->name);
		if (type_def == (Type_def)0) {
			msg_out(convert->msg, statement->position,
				"Type `%s' is not defined!", type_ref->name);
		} else {
			type_def_structure_needed(type_def);

			if (type_def->kind != Type_kind_variant) {
				msg_out(convert->msg, statement->position,
				    "extract statement needs a variant type; "
				    "type `%s' is not a variant type!",
				    type_ref->name);
			}

			/* Verify that tag type is an enumeration type: */
			type_variant = type_def->value.variant;
			type_ref = type_variant->tag_field->type_ref;
			type_def =
			    type_def_table_lookup(convert->type_def_table,
						  type_ref->name);
			if (type_def == (Type_def)0) {
				msg_out(convert->msg, statement->position,
					"Type `%s' is not defined!",
					type_ref->name);
			} else {
				type_def_structure_needed(type_def);
				if (type_def->kind != Type_kind_enumeration) {
					msg_out(convert->msg,
						statement->position,
						"extract statement needs an "
						"enumeration type; type `%s' "
						"is not a enumeration type!",
					type_ref->name);
				}
			}
		}

		object_table = convert->object_table;
		tags = extract->tags;
		VEC_LOOP(Statement_tag, tags, tag) {
			if (tag->var_type_ref != (Type_ref)0) {
				var = table_lookup(Str, Var,
						   convert->var_table,
						   tag->var_name);
				if (var == (Var)0) {
					var = heap_allocate(convert->heap,
							    Var);
					var->comment = (Str)0;
					var->name = tag->var_name;
					var->position = tag->position;
					var->type_ref = tag->var_type_ref;
					(void)table_insert(Str, Var,
							   convert->var_table,
							   var->name, var);
					vec_append(Var,
						   convert->routine->vars,
						   var);
				} else {
					if (!type_ref_equal(var->type_ref,
							tag->var_type_ref)) {
						msg_out(convert->msg,
						    tag->position,
						    "%s is of type %r not %r",
						    var->name, var->type_ref,
						    tag->var_type_ref);
					}
				}
			}
			tag_names = tag->tag_names;
			VEC_LOOP(Str, tag_names, tag_name) {
				(void)object_ref_lookup(object_table,
							tag_name,
							type_def->type_ref);
			}
			statement_list_convert(tag->statements, convert);
		}
		break;
	      }
	    case Statement_kind_if:
	      {
		Call			call;
		Vec(Call)		calls;
		Statement_cond		cond;
		Vec(Statement_cond)	conds;

		conds = statement->value.xif;
		VEC_LOOP(Statement_cond, conds, cond) {
			if (cond->exp != (Exp)0) {
				calls = vec_create(Call, convert->heap);
				call = call_exp_convert(convert, cond->exp,
						convert->type_refs_logical,
						calls);
				cond->exp->call = call;
				cond->calls = calls;
			}
			statement_list_convert(cond->statements, convert);
		}
		break;
	      }
	    case Statement_kind_loop:
		statement_list_convert(statement->value.loop->statements,
				       convert);
		break;
	    case Statement_kind_initialize:
	      {
		Vec(Call)	calls;
		Type_ref	exp_type_ref;
		Heap		heap;
		Statement_initialize initialize;
		Str		name;
		Var		var;
		Type_ref	type_ref;
		Type_refs	type_refs;

		initialize = statement->value.initialize;
		heap = convert->heap;
		name = initialize->var_name;
		type_ref = initialize->var_type_ref;
		var = table_lookup(Str, Var, convert->var_table, name);
		if (var == (Var)0) {
			if (type_ref == (Type_ref)0) {
				msg_out(convert->msg, statement->position,
					"%s is an undefined variable", name);
			} else {
				var = heap_allocate(heap, Var);
				var->comment = (Str)0;
				var->name = name;
				var->position = statement->position;
				var->type_ref = type_ref;
				assert(table_insert(Str, Var,
						    convert->var_table,
						    name, var) == 0);
				vec_append(Var, convert->routine->vars, var);
			}
		} else if ((type_ref != (Type_ref)0) &&
			   (!type_ref_equal(type_ref, var->type_ref))) {
			msg_out(convert->msg, statement->position,
				"Variable %s has type of %r, not %r",
				name, var->type_ref, type_ref);
		}
		type_ref = var->type_ref;

		calls = vec_create(Call, heap);
		if (initialize->exp != (Exp)0) {
			initialize->exp->call =
				call_exp_convert(convert, initialize->exp,
						 convert->type_refs_empty,
						 calls);
			type_refs = initialize->exp->call->type_refs;
			if (type_refs_size(type_refs) == 1) {
				exp_type_ref = type_refs_fetch(type_refs, 0);
				if (!type_ref_equal(type_ref, exp_type_ref)) {
					msg_out(convert->msg,
						statement->position,
						"%s has type of %r, not %r",
						name, exp_type_ref, type_ref);
				}
			} else {
				msg_out(convert->msg, statement->position,
					"Expression should return "
					"only one value");
			}
		}
		statement_list_convert(initialize->statements, convert);
		statement->calls = calls;
		break;
	      }
	    case Statement_kind_none:
		break;
	    case Statement_kind_return:
	      {
		Vec(Call)	calls;
		Exp		exp;
		Type_ref	exp_type_ref;
		Type_refs	exp_type_refs;
		Vec(Exp)	exps;
		Heap		heap;
		int		index;
		Type_refs	return_type_refs;
		Type_refs	returns;
		int		size;
		Type_ref	type_ref;
		Type_tables	type_tables;
		Type_refs	type_refs;

		heap = convert->heap;
		type_tables = convert->type_tables;
		calls = vec_create(Call, heap);
		exps = statement->value.exps;
		returns = convert->routine->returns;
		size = vec_size(Exp, exps);
		if (size != type_refs_size(returns)) {
			msg_out(convert->msg, statement->position,
			      "Routine is supposed to return %d values, not %d",
			      type_refs_size(returns), size);
			break;
		}
		return_type_refs = type_refs_empty_create(type_tables);
		for (index = 0; index < size; index++) {
			exp = vec_fetch(Exp, exps, index);
			type_ref = type_refs_fetch(returns, index);
			return_type_refs = type_refs_append(return_type_refs,
							    type_ref,
							    type_tables);
			type_refs = type_ref->type_refs;
			exp->call = call_exp_convert(convert, exp,
						     type_refs, calls);
			exp_type_refs = exp->type_refs;
			if (type_refs_size(exp_type_refs) == 0) {
				msg_out(convert->msg, statement->position,					  "return statement returning expression"
				  " with no value instead type %s",
				  type_ref->name);
			} else {
				exp_type_ref =
				  type_refs_fetch(exp_type_refs, 0);
				if (!type_ref_equal(type_ref, exp_type_ref)) {
					msg_out(convert->msg,
					statement->position,
					  "return statement returning type %s"
					  " instead of type %s",
					  exp_type_ref->name,
					  type_ref->name);
				}
			}
		}
		if (type_refs_size(return_type_refs) > 1) {
			type_refs_multiple_needed(return_type_refs,
						  convert->type_tables);
		}
		statement->calls = calls;
		break;
	      }
	    case Statement_kind_switch:
	      {
		Statement_case		xcase;
		Vec(Statement_case)	cases;
		Vec(Call)		calls;
		Exp			exp;
		Vec(Exp)		exps;
		Heap			heap;
		Statement_switch	xswitch;
		Type_def		type_def;
		Type_ref		type_ref;
		Type_refs		type_refs;

		heap = convert->heap;
		xswitch = statement->value.xswitch;
		exp = xswitch->exp;
		statement_exp_convert(statement, exp, convert);

		/* Make sure that the type definition exists and is correct: */
		type_refs = exp->type_refs;
		assert(type_refs != (Type_refs)0);
		if (type_refs_size(type_refs) != 1) {
			msg_out(convert->msg, statement->position,
				"Switch expression returns %d types",
				type_refs_size(type_refs));
			break;
		}
		type_ref = type_refs_fetch(type_refs, 0);
		type_def = type_def_table_lookup(convert->type_def_table,
						 type_ref->name);
		if (type_def == (Type_def)0) {
			msg_out(convert->msg, statement->position,
				"Type `%s' is not defined!", type_ref->name);
			break;
		} else {
			type_def_structure_needed(type_def);
		}
		if (type_def->kind != Type_kind_enumeration) {
			msg_out(convert->msg, statement->position,
				"Switch statement needs a enumeration type; "
				"type `%s' is not a enumeration type!",
				type_ref->name);
		}

		calls = vec_create(Call, heap);
		cases = xswitch->cases;
		VEC_LOOP(Statement_case, cases, xcase) {
			exps = xcase->exps;
			VEC_LOOP(Exp, exps, exp) {
				exp->call = call_exp_constant(convert, exp,
							      type_refs, calls);
			}
			statement_list_convert(xcase->statements, convert);
		}
		assert(vec_empty(Call, calls));
		statement->calls = calls;
		break;
	      }
	    case Statement_kind_until:
	    case Statement_kind_while:
		statement_logical_convert(statement, convert);
		break;
	    case Statement_kind_yield:
	      {
		Vec(Call)	calls;
		Exp		exp;
		Vec(Exp)	exps;
		Heap		heap;
		int		index;
		int		size;
		Type_ref	type_ref;
		Type_refs	type_refs;
		Type_refs	yields;

		heap = convert->heap;
		calls = vec_create(Call, heap);
		exps = statement->value.exps;
		yields = convert->routine->yields;
		size = vec_size(Exp, exps);
		assert(size == type_refs_size(yields));
		for (index = 0; index < size; index++) {
			exp = vec_fetch(Exp, exps, index);
			type_ref = type_refs_fetch(yields, index);
			type_refs = type_ref->type_refs;
			exp->call = call_exp_convert(convert, exp,
						     type_refs, calls);
		}
		statement->calls = calls;
		break;
	      }
	    default:
		error_abort("Bad statement");
	}
}

/*
 * statement_exp_convert(statement, exp, convert)
 *	This routine will extract the call tree from "exp" and store it in
 *	"exp"->call.  Any preceeding call operations are stored in
 *	"statement"->calls.
 */
void
statement_exp_convert(
	Statement	statement,
	Exp		exp,
	Convert		convert)
{
	Call		call;
	Vec(Call)	calls;

	calls = vec_create(Call, convert->heap);
	call = call_exp_convert(convert, exp, convert->type_refs_empty, calls);
	exp->call = call;
	statement->calls = calls;
}

/*
 * statement_list_convert(statements, convert)
 *	This routine will convert all of the expressions in "statements"
 *	into call trees.
 */
void
statement_list_convert(
	Vec(Statement)	statements,
	Convert		convert)
{
	Statement	statement;

	VEC_LOOP(Statement, statements, statement) {
		statement_convert(statement, convert);
	}
}

/*
 * statement_logical_convert(statement)
 *	This routine will convert the expression assoicated with "statement"
 *	into an call tree of type logical.
 */
LOCAL void
statement_logical_convert(
	Statement	statement,
	Convert		convert)
{
	Call		call;
	Vec(Call)	calls;
	Exp		exp;

	exp = statement->value.exp;
	calls = vec_create(Call, convert->heap);
	call = call_exp_convert(convert, exp,
				convert->type_refs_logical, calls);
	exp->call = call;
	statement->calls = calls;
}
