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

/*
 * Copyright (c) 1990, 1991, 1992, 1993, 1995 by Wayne C. Gramlich.
 * All rights reserved.
 *
 * Permission to use, copy, modify, distribute, and sell this software
 * for any purpose is hereby granted without fee provided that the above
 * copyright notice and this permission are retained.  The author makes
 * no representations about the suitability of this software for any purpose.
 * It is provided "as is" without express or implied warranty.
 */

/*
 * This file contains the routines responsible for creating call objects.
 */

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

#ifndef GEN_DEFS_H
#include "gen_defs.h"
#endif

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

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

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

#ifndef NEED_EXPORTS_H
#include "need_exports.h"
#endif

#ifndef OBJECT_DEFS_H
#include "object_defs.h"
#endif

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

#ifndef STR_EXPORTS_H
#include "str_exports.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 VECTOR_DEFS_H
#include "vector_defs.h"
#endif

LOCAL Call	call_create(Convert, Call_type, int, Type_refs);

/*
 * call_assign_create(convert, left_call, right_call)
 *	This routine will create and return an assignment node consisting of
 *	"left_call" and "right_call".  "left_call" must be a variable.
 */
Call
call_assign_create(
	Convert		convert,
	Call		left_call,
	Call		right_call)
{
	Call		call;
	Call_assign	call_assign;

	if (!type_refs_equal(left_call->type_refs,
				 right_call->type_refs)) {
		msg_out(convert->msg, left_call->position,
			"Left type of ':=' is %q and right type is %q",
			left_call->type_refs, right_call->type_refs);
	}

	call_assign = heap_allocate(convert->heap, Call_assign);
	call_assign->left = left_call;	
	call_assign->right = right_call;
	call = call_create(convert, Call_type_assign,
			   left_call->position, convert->type_refs_empty);
	call->value.assign = call_assign;
	return call;
}

/*
 * call_binary_create(convert, call_type, left_call, right_call)
 *	This routine will create and return a binary call node consisting
 *	of "left_call" and "right_call".
 */
Call
call_binary_create(
	Convert		convert,
	Call_type	call_type,
	Call		left_call,
	Call		right_call)
{
	Call		call;
	Call_binary	call_binary;

	call_binary = heap_allocate(convert->heap, Call_binary);
	call_binary->left = left_call;
	call_binary->right = right_call;
	call = call_create(convert, call_type, left_call->position,
			   convert->type_ref_logical->type_refs);
	call->value.binary = call_binary;
	return call;
}

/*
 * call_cast_create(convert, call, type_ref)
 *	This routine will return a cast call node consisting of "call"
 *	and "type_ref".
 */
Call
call_cast_create(
	Convert		convert,
	Call		call,
	Type_ref	type_ref)
{
	Heap		heap;
	Call_cast	call_cast;

	heap = convert->heap;
	call_cast = heap_allocate(heap, Call_cast);
	call_cast->call = call;
	call_cast->type_ref = type_ref;
	call = call_create(convert, Call_type_cast,
			   call->position, call->type_refs);
	call->value.cast = call_cast;
	return call;
}

/*
 * call_create(convert, type, position, type_refs)
 *	This routine will return a new Call object allocated from "heap"
 *	with a type of "type" and a position of "position".  Immediately
 *	after creating a call object, the value field must be assigned.
 */
Call
call_create(
	Convert		convert,
	Call_type	type,
	int		position,
	Type_refs	type_refs)
{
	Call		call;

	call = heap_allocate(convert->heap, Call);
	call->type = type;
	call->position = position;
	call->type_refs = type_refs;
	return call;
}

/*
 * call_error_create(convert, position, error)
 *	This routine will return an error call node.
 */
Call
call_error_create(
	Convert		convert,
	int		position,
	Str		error)
{
	Call		call;

	call = call_create(convert, Call_type_error,
			   position, convert->type_refs_empty);
	call->value.error = error;
	return call;
}

/*
 * call_if_create(convert, cond_call, true_call, false_call)
 *	This routine will create and return an arithmetic if node
 *	consisting of "cond_call", "true_call", and "false_call".
 */
Call
call_if_create(
	Convert		convert,
	Call		cond_call,
	Call		true_call,
	Call		false_call)
{
	Call		call;
	Call_if		call_if;

	call_if = heap_allocate(convert->heap, Call_if);
	call_if->condition = cond_call;
	call_if->true = true_call;
	call_if->false = false_call;
	call = call_create(convert, Call_type_if,
			   cond_call->position, true_call->type_refs);
	call->value.xif = call_if;
	return call;
}

/*
 * call_integer_create(convert, number)
 *	This routine will create an return an integer call object.
 */
Call
call_integer_create(
	Convert		convert,
	int		number,
	int		position)
{
	Call		call;

	call = call_create(convert, Call_type_integer, position,
			   convert->type_ref_unsigned->type_refs);
	call->value.integer = number;
	return call;
}

/*
 * call_invoke_check(convert, call, actuals, type_proto)
 *	This routine will verify that the types of "actuals" matches the
 *	prototype associated with "call" (which is of type "type_proto".)
 *	1 is returned if any error is detected; otherwise 0 is returned.
 */
LOCAL int
call_invoke_check(
	Convert		convert,
	Call		call,
	Vec(Call)	actuals,
	Type_proto	type_proto)
{
	Call		actual_call;
	Type_ref	actual_type_ref;
	Type_refs	actual_type_refs;
	int		actual_type_refs_size;
	Type_ref	take_type_ref;
	Type_refs	take_type_refs;
	int		take_type_refs_size;
	int		index;

	/* Assemble the list of actual types: */
	actual_type_refs = type_refs_empty_create(convert->type_tables);
	VEC_LOOP(Call, actuals, actual_call) {
		actual_type_refs = type_refs_concat(actual_type_refs,
						    actual_call->type_refs,
						    convert->type_tables);
	}
	actual_type_refs_size = type_refs_size(actual_type_refs);

	/* Check each argument: */
	take_type_refs = type_proto->takes;
	take_type_refs_size = type_refs_size(take_type_refs);
	if (take_type_refs_size != actual_type_refs_size) {
		msg_out(convert->msg, call->position,
			"Routine expected %d arguments, but got %d arguments",
			take_type_refs_size, actual_type_refs_size);
		return 1;
	}
	for (index = 0; index < take_type_refs_size; index++) {
		take_type_ref = type_refs_fetch(take_type_refs, index);
		actual_type_ref = type_refs_fetch(actual_type_refs, index);
		if (!type_ref_equal(take_type_ref, actual_type_ref)) {
			msg_out(convert->msg, call->position,
			    "Argument %d has type of %r instead of type %r",
			    index + 1, actual_type_ref, take_type_ref);
			return 1;
		}
	}
	return 0;
}

/*
 * call_invoke_create(convert, call, actuals, type_proto)
 *	This routine will create an return a Call_invoke object.
 */
/* ARGSUSED */
Call
call_invoke_create(
	Convert		convert,
	Call		call,
	Vec(Call)	actuals)
{
	Call_invoke	call_invoke;
	Call_routine	call_routine;
	Type_ref	call_type_ref;
	Type_refs	call_type_refs;
	Type_proto	call_type_proto;
	Call		new_call;

	if (call->type == Call_type_routine) {
		call_routine = call->value.routine;
		call_routine->routine_ref->constant--;
	}

	/* Get the routine prototype: */
	call_type_refs = call->type_refs;
	if (type_refs_size(call_type_refs) != 1) {
		msg_out(convert->msg, call->position,
			"Trying to invoke a non-routine");
		return call_error_create(convert, call->position,
					 "Trying to invoke a non-routine");
	}
	call_type_ref = type_refs_fetch(call_type_refs, 0);
	call_type_proto = type_proto_from_type_ref(call_type_ref,
						   convert->type_tables);
	if (call_type_proto == (Type_proto)0) {
		msg_out(convert->msg, call->position,
			"Trying to invoke a non-routine");
		return call_error_create(convert, call->position,
					 "Trying to invoke a non-routine");
	}

	if (call_invoke_check(convert, call, actuals, call_type_proto)) {
		msg_out(convert->msg, call->position, "Argument mismatch");
		return call_error_create(convert, call->position,
					 "Argument mismatch");
	}

	call_invoke = heap_allocate(convert->heap, Call_invoke);
	call_invoke->call = call;
	call_invoke->actuals = actuals;
	new_call = call_create(convert, Call_type_invoke,
			       call->position, call_type_proto->returns);
	new_call->value.invoke = call_invoke;
	return new_call;
}

/*
 * call_object_create(convert, object_ref, position)
 *	This routine will create and return a const call node consisting
 *	of "object_ref".
 */
Call
call_object_create(
	Convert		convert,
	Object_ref	object_ref,
	int		position)
{
	Call		call;

	if (type_ref_is_parameterized(object_ref->type_ref)) {
		Need_table	need_table;

		need_table = convert->routine->need_table;
		need_table_object_insert(need_table,
					 object_ref->name,
					 object_ref->type_ref,
					 object_ref->actual_type_ref);
	}
	call = call_create(convert, Call_type_object,
			   position, object_ref->type_ref->type_refs);
	call->value.object = object_ref;
	return call;
}

/*
 * call_routine_create(convert, routine_ref, position)
 *	This routine will return a call routine object with a value
 *	of "routine_ref" and a position of "position".
 */
Call
call_routine_create(
	Convert		convert,
	Routine_ref	routine_ref,
	int		position)
{
	Call		call;
	Call_routine	call_routine;
	Routine		routine;

	call_routine = heap_allocate(convert->heap, Call_routine);
	call_routine->routine_ref = routine_ref;

	routine_ref->constant++;

	if (!type_refs_is_empty(routine_ref->type_ref->parameters)) {
		routine = convert->routine;
		/*XXX: Argument types should be checked! */
		need_table_routine_insert(routine->need_table,
				routine_ref->name,
				routine_ref->type_ref,
				routine_ref->routine_entry->type_proto); /*Yes*/
	}

	call = call_create(convert, Call_type_routine, position,
			   routine_ref->type_proto->type_ref->type_refs);
	call->value.routine = call_routine;
	return call;
}

/*
 * call_multi_create(convert, invoke, type_refs)
 *	This routine will return a multiple assignment call object.
 */
Call
call_multi_create(
	Convert		convert,
	Call		invoke,
	Type_refs	type_refs)
{
	Call		call;
	Call_multi	call_multi;

	call_multi = heap_allocate(convert->heap, Call_multi);
	call_multi->invoke = invoke;
	call_multi->type_refs = type_refs;
	call_multi->vars = vec_create(Call, convert->heap);
	type_refs = invoke->type_refs;
	call = call_create(convert, Call_type_multi,
			   invoke->position, type_refs);
	type_refs_multiple_needed(type_refs, convert->type_tables);
	call->value.multi = call_multi;
	return call;
}

/*
 * call_not_create(convert, call)
 *	This routine will create and return a not call for "call".
 */
Call
call_not_create(
	Convert		convert,
	Call		call)
{
	Call		not_call;

	not_call = call_create(convert, Call_type_not, call->position,
			       convert->type_ref_logical->type_refs);
	not_call->value.not = call;
	return not_call;
}

/*
 * call_string_create(convert, string, position)
 *	This routine will return a string call object.
 */
Call
call_string_create(
	Convert		convert,
	Str		string,
	int		position)
{
	Call		call;

	call = call_create(convert, Call_type_string, position,
			   convert->type_ref_string->type_refs);
	call->value.string = string;
	return call;
}


/*
 * call_temp_create(convert, position, type_ref)
 *	This routine will return a temporary variable allocated from
 *	"convert" with a type of "type_ref".
 */
Call
call_temp_create(
	Convert		convert,
	int		position,
	Type_ref	type_ref)
{
	Call		call;
	Call_temp	call_temp;
	Heap		heap;
	Type_refs	type_refs;

	heap = convert->heap;
	type_refs = type_ref->type_refs;
	call_temp = heap_allocate(heap, Call_temp);
	call_temp->number = vec_size(Type_ref, convert->temps);
	vec_append(Type_ref, convert->temps, type_ref);
	call = call_create(convert, Call_type_temp, position, type_refs);
	call->value.temp = call_temp;
	return call;
}

/*
 * call_var_create(convert, name, position, type_refs)
 *	This routine will return a call reference to a variable named "name"
 *	at "position" and of type "type_refs".
 */
Call
call_var_create(
	Convert		convert,
	Str		name,
	int		position,
	Type_refs	type_refs)
{
	Call		call;
	Call_var	call_var;
	Heap		heap;

	heap = convert->heap;
	call_var = heap_allocate(heap, Call_var);
	call_var->name = name;
	call = call_create(convert, Call_type_var, position, type_refs);
	call->value.var = call_var;
	return call;
}

/*
 * convert_create(gen)
 *	This routine will create an return a Convert object using "gen".
 */
Convert
convert_create(
	Gen		gen)
{
	Convert		convert;
	Heap		heap;
	Type_tables	type_tables;

	heap = gen->heap;
	type_tables = gen->type_tables;
	convert = heap_allocate(heap, Convert);
	convert->flags = gen->flags;
	convert->heap = heap;
	convert->msg = gen->msg;
	convert->object_table = gen->object_table;
	convert->routine = (Routine)0;
	convert->synonyms = table_create(Str, Type_ref,
					 10, strequal, strhash,
					 (Type_ref)0, heap);
	convert->temps = gen->temps;
	convert->type_def_table = gen->type_def_table;
	convert->type_tables = gen->type_tables;
	convert->routine_table = gen->routine_table;
	convert->var_table = (Var_table)0;

	convert->type_ref_heap = type_ref_create("heap", type_tables);
	convert->type_ref_in_stream = type_ref_create("in_stream",
						      type_tables);
	convert->type_ref_global = type_ref_create("global__", type_tables);
	convert->type_ref_logical = type_ref_create("logical", type_tables);
	convert->type_ref_out_stream = type_ref_create("out_stream",
						       type_tables);
	convert->type_ref_integer = type_ref_create("integer",
						     type_tables);
	convert->type_ref_string = type_ref_create("string", type_tables);
	convert->type_ref_unsigned = type_ref_create("unsigned",
						       type_tables);
	convert->type_refs_empty = type_refs_empty_create(type_tables);
	convert->type_refs_logical = type_refs_append(convert->type_refs_empty,
						convert->type_ref_logical,
						type_tables);

	convert->type_tables = type_tables;
	gen->convert = convert;
	return convert;
}

