/* $Id: rt_methd.c,v 1.18 2009/02/28 19:18:10 kiesling Exp $ */

/*
  This file is part of Ctalk.
  Copyright © 2005-2009  Robert Kiesling, ctalk@ctalklang.org.
  Permission is granted to copy this software provided that this copyright
  notice is included in all source code modules.

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

  This library 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
  Lesser General Public License for more details.

  You should have received a copy of the GNU General Public License
  along with this program; if not, write to the Free Software Foundation, 
  Inc., 51 Franklin St., Fifth Floor, Boston, MA 02110-1301 USA.
*/

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "object.h"
#include "message.h"
#include "ctalk.h"
#include "rtinfo.h"
#include "typeof.h"

extern RT_INFO rtinfo, *__call_stack[MAXARGS+1];

OBJECT *__ctalk_method (char *__object_name, OBJECT *(*__cfunc)()) {

  OBJECT *__receiver_obj_, *__method_rcvr_obj_;
  OBJECT *__class_obj_;
  OBJECT *__result_obj_ = NULL;
  METHOD *method;
  int i;

  /*
   *  If the object name refers to a parameter, then it is only
   *  stored the method, and we have to find the actual argument
   *  by looking for the parameter name.  Method arguments are 
   *  not stored on the receiver stack, until just before we 
   *  call this method.
   *
   *  If the object name is a C lexical type, then simply create
   *  an object. 
   */

  switch (_lextype (__object_name))
    {
    case LITERAL_T:
      __receiver_obj_ =
	__ctalkCreateObjectInit (__object_name, STRING_CLASSNAME,
				 STRING_SUPERCLASSNAME,
				 RECEIVER_VAR,
				 __object_name);
      break;
    case INTEGER_T:
    case LONG_T:
      __receiver_obj_ =
	__ctalkCreateObjectInit (__object_name, INTEGER_CLASSNAME,
				 INTEGER_SUPERCLASSNAME,
				 RECEIVER_VAR,
				 __object_name);
      break;
    case LONGLONG_T:
      __receiver_obj_ =
	__ctalkCreateObjectInit (__object_name, LONGINTEGER_CLASSNAME,
				 LONGINTEGER_SUPERCLASSNAME,
				 RECEIVER_VAR,
				 __object_name);
      break;
    case FLOAT_T:
    case DOUBLE_T:
      __receiver_obj_ =
	__ctalkCreateObjectInit (__object_name, FLOAT_CLASSNAME,
				 FLOAT_SUPERCLASSNAME,
				 RECEIVER_VAR,
				 __object_name);
      break;
    case LITERAL_CHAR_T:
      __receiver_obj_ =
	__ctalkCreateObjectInit (__object_name, CHARACTER_CLASSNAME,
				 CHARACTER_SUPERCLASSNAME,
				 RECEIVER_VAR,
				 __object_name);
      break;
    default:
      if (((__receiver_obj_ = __ctalk_get_object(__object_name, NULL)) 
	   == NULL) &&
	  ((__receiver_obj_ = __ctalk_getParamByName (__object_name)) 
	   == NULL) &&
	  ((__receiver_obj_ = __ctalkGetClass (__object_name)) 
	   == NULL) &&
	  ((__receiver_obj_ = __ctalkFindClassVariable (__object_name, FALSE))
	   == NULL)) 
	_error ("__ctalk_method: Unknown receiver %s.\n", __object_name);
      break;
    }
  __class_obj_ = __receiver_obj_->__o_class;
  __method_rcvr_obj_ = __receiver_obj_;

  if (((method = 
       __ctalkFindInstanceMethodByFn (&__method_rcvr_obj_, __cfunc, FALSE))
      == NULL) &&
      ((method = 
	__ctalkFindClassMethodByFn (&__method_rcvr_obj_, __cfunc, FALSE))
       == NULL)) {
    /*
     *  Experimental - if the receiver is the member of a collection, 
     *  then look for the method in the parent object's class.  Then, 
     *  if the parent object is an instance or class variable, look 
     *  for the method in the parent object's value class.
     */
    if (__receiver_obj_ -> __o_p_obj && 
	IS_OBJECT (__receiver_obj_ -> __o_p_obj)) {
      __method_rcvr_obj_ = __receiver_obj_ -> __o_p_obj;
      if (((method = 
	    __ctalkFindInstanceMethodByFn (&__method_rcvr_obj_, 
					   __cfunc, FALSE)) == NULL) &&
	  ((method = 
	    __ctalkFindClassMethodByFn (&__method_rcvr_obj_, 
					__cfunc, FALSE))  == NULL)) {
	OBJECT *p_value_obj;
	if ((p_value_obj = 
	     __ctalkGetInstanceVariable (__receiver_obj_ -> __o_p_obj,
					 "value", FALSE)) != NULL) {
	  __method_rcvr_obj_ = p_value_obj;
	  if (((method = 
		__ctalkFindInstanceMethodByFn (&__method_rcvr_obj_, 
					       __cfunc, FALSE)) == NULL) &&
	      ((method = 
		__ctalkFindClassMethodByFn (&__method_rcvr_obj_, 
					    __cfunc, FALSE))  == NULL)) {
	    _warning ("__ctalk_method: Undefined method.\n");
	    return NULL;
	  } 
	} else {
	  _warning ("__ctalk_method: Undefined method.\n");
	  return NULL;
	}
      } 
    } else {    
      _warning ("__ctalk_method: Undefined method.\n");
      return NULL;
    }
  }

  if ((method -> n_args != method -> n_params) &&
      (method -> varargs == 0)) {
    /*
     *  Handle exception immediately in case called by a top-level
     *  function.
     */
    __ctalkExceptionInternal (NULL, wrong_number_of_arguments_x, method -> name);
    __ctalkHandleRunTimeExceptionInternal ();
    return null_result_obj (method, ARG_VAR);
  }

  method -> arg_frame_top = __ctalkGetArgPtr () + 1;
  method -> rcvr_frame_top = __ctalkGetReceiverPtr () + 1;

  __save_rt_info (__receiver_obj_, __class_obj_, NULL, method, __cfunc, False);

  __ctalk_receiver_push (__receiver_obj_);

  __result_obj_ = (__cfunc)();

  __ctalk_receiver_pop ();

  __ctalk_methodReturn (__receiver_obj_, method, __result_obj_);

  if (IS_CONSTRUCTOR (method)) {
    OBJECT *__arg_object;
    VARENTRY *t_local, *v_next;
    int call_level;
    /*
     *  The interpreter adds a __ctalk_set_local () call in the
     *  caller's init block, so it isn't needed here.
     *
     *  The object must be removed from the method, so it 
     *  isn't bollixed the next time the method is called.
     *
     *  If the argument is still on the stack, remove it.
     */
    if (__result_obj_ && IS_OBJECT(__result_obj_))
      __ctalk_dictionary_add (__result_obj_);

    __arg_object = __ctalk_arg_pop ();
    __ctalk_arg_push (__arg_object);
    if (__result_obj_ == __arg_object) {
      if (!strcmp (method -> rcvr_class_obj -> __o_name, OBJECT_CLASSNAME)) {
	__ctalk_arg_pop ();
      }
      method -> args[method -> n_args - 1] = NULL;
      --(method -> n_args);
    }

    for (t_local = method -> local_objects.vars; 
	 t_local; t_local = t_local -> next) {
      if (t_local -> var_object == __result_obj_) {
	if (__result_obj_ == method -> local_objects.vars->var_object) {
	  v_next = method -> local_objects.vars -> next;
	  delete_varentry (method -> local_objects.vars);
	  method -> local_objects.vars = t_local = v_next;
	} else {
	  if (t_local -> prev) t_local -> prev -> next = t_local -> next;
	  if (t_local -> next) t_local -> next -> prev = t_local -> prev;
	  delete_varentry (t_local);
	}
      }
      if (!t_local) 
	break;
    }

    /*
     *  If the calling method is also a constructor, then 
     *  set the result object's class and superclass to 
     *  the calling method.  Also set the value variable's
     *  class and superclass.
     *
     *  The caller is two call frames up from the call 
     *  stack pointer.
     *
     *  If the caller is a _rt_fn, then skip.
     */
    if ((call_level = __get_call_stack_ptr ()) < (MAXARGS - 1) &&
	(__call_stack[call_level+2] -> _rt_fn == NULL)) {
      METHOD *calling_method;
      OBJECT *calling_rcvr_class_obj;
      calling_rcvr_class_obj = 
	__call_stack[call_level + 2] -> rcvr_class_obj;
      if ((calling_method = 
	   __ctalkGetInstanceMethodByFn (calling_rcvr_class_obj, 
					 __call_stack[call_level+2]->method_fn,
					 FALSE)) != NULL) {
	if (IS_CONSTRUCTOR(calling_method)) {
	  OBJECT *result_object_value_var;
	  result_object_value_var = __result_obj_ -> instancevars;
	  strcpy (__result_obj_ -> __o_classname, 
		  calling_rcvr_class_obj -> __o_name);
	  strcpy (__result_obj_ -> __o_superclassname, 
		  calling_rcvr_class_obj -> __o_superclassname);
	  __result_obj_->__o_class = calling_rcvr_class_obj -> __o_class;
	  __result_obj_->__o_superclass = 
	    calling_rcvr_class_obj -> __o_superclass;
	}
      }    
    }
  }

  for (i = 0; i < method -> n_args; i++) {
    method -> args[i] = NULL;
  }
  method -> n_args = 0;

  __restore_rt_info ();

  return __result_obj_;
}

OBJECT *__ctalkInlineMethod (OBJECT *rcvr, METHOD *m) {
  OBJECT *__result_object, *__rcvr_class_object, 
    *__method_class_object, *(*__cfunc)();
  int __save_stack_ptr, __s;

  __cfunc = m -> cfunc;
  __rcvr_class_object = rcvr->__o_class;
  /*
   *  At the present the only use of a the method class is
   *  within an inline function, where the method's receiver class
   *  might be different than the class of the actual receiver.
   *  In these cases, the method's class is the receiver class of
   *  the previous call stack entry.
   */
  __method_class_object = m -> rcvr_class_obj;
  __save_stack_ptr = __get_call_stack_ptr ();
  __save_rt_info (rcvr, __rcvr_class_object, __method_class_object, 
		  m, __cfunc, True);
  __ctalk_receiver_push_ref (rcvr);
  __result_object = (__cfunc)();
  __ctalk_receiver_pop_deref ();
  /*
   *  Methods that don't have a method return macro can sometimes
   *  have a __ctalk_exitFn () call, so check if we've already
   *  removed the call info.
   */
  if ((__s = __get_call_stack_ptr ()) < __save_stack_ptr)
    __restore_rt_info ();

  return __result_object;
}

METHOD *__ctalkGetInstanceMethodByName (OBJECT *o, 
					const char *name, int warn) {
  OBJECT *class, *value_var;
  METHOD *m = NULL, *m_value = NULL, *m_obj = NULL;

  if ((value_var = __ctalkGetInstanceVariable (o, "value", FALSE)) != NULL){
    if ((class=
	 ((IS_CLASS_OBJECT(value_var)) ? value_var : value_var->__o_class))
	!= NULL) {
      for (m = class -> instance_methods; m; m = m -> next) {
	if (!strcmp (m -> name, name))
	  m_value = m;
	if (!m -> next)
	  break;
      }
      if (!m_value) {
	if (class -> __o_superclass)
	  if ((m = __ctalkGetInstanceMethodByName 
	       (class -> __o_superclass, name, warn)) != NULL)
	    m_value = m;
      }
    }
  }

  class=((IS_CLASS_OBJECT(o)) ? o : o->__o_class);
  if (!class) {
    if (warn) {
      _warning ("__ctalkGetInstanceMethodByName: Undefined class.\n",
		o -> __o_classname);
    }
    return NULL;
  }
  for (m = class -> instance_methods; m; m = m -> next) {
    if (!strcmp (m -> name, name))
      m_obj = m;
    if (!m -> next)
      break;
  }
  if (!m_obj) {
    if (class -> __o_superclass)
      if ((m = __ctalkGetInstanceMethodByName 
	   (class -> __o_superclass, name, warn)) != NULL)
	m_obj = m;
  }
  return 
    (((m_value && (m_value->rcvr_class_obj==value_var->__o_class))? m_value: 
      ((m_obj && (m_obj->rcvr_class_obj==o->__o_class)) ? m_obj :
       ((m_value) ? m_value : 
	((m_obj) ? m_obj : NULL)))));
}

METHOD *__ctalkGetInstanceMethodByFn (OBJECT *rcvr, OBJECT *(*fn)(void), 
				      int warn) {

  OBJECT *class;
  METHOD *m;

  if (!rcvr || !IS_OBJECT(rcvr))
    return NULL;

  class = rcvr -> __o_class;

  for (m = class -> instance_methods; m; m = m -> next) {
    if (m -> cfunc == fn)
      return m;
    if (!m -> next)
      break;
  }
  
  if (class -> __o_superclass)
    return __ctalkGetInstanceMethodByFn (class -> __o_superclass, fn, warn);
  else
    return NULL;
}

METHOD *__ctalkGetClassMethodByName (OBJECT *o, const char *name, int warn) {

  OBJECT *class, *value_var;
  METHOD *m;

  if (!strcmp (o -> __o_classname, "Expr")) {
    if ((value_var = __ctalkGetInstanceVariable (o, "value", FALSE)) 
 	== NULL) {
      if (warn) {
	_warning ("__ctalkGetInstanceMethodByName: %s: Undefined value.\n",
		  o -> __o_name);
      }
      return NULL;
    }

    if (!IS_CLASS_OBJECT(value_var)) 
      class = __ctalk_get_object (value_var -> __o_classname, "Class");
    else
      class = value_var;
    
   } else {
     if (!IS_CLASS_OBJECT(o)) 
       class = __ctalk_get_object (o -> __o_classname, "Class");
     else 
       class = o;
   }

   if (!class) {
     if (warn) {
       _warning ("__ctalkGetClassMethodByName: Undefined class.\n",
 		o -> __o_classname);
     }
     return NULL;
   }

   for (m = class -> class_methods; m; m = m -> next) {
     if (!strcmp (m -> name, name))
       return m;
     if (!m -> next)
       break;
   }
 
   if (class -> __o_superclass)
     return __ctalkGetClassMethodByName (class -> __o_superclass, name, warn);
   else
     return NULL;
}

METHOD *__ctalkGetClassMethodByFn (OBJECT *rcvr, OBJECT *(*fn)(void), 
				   int warn) {

  OBJECT *class;
  METHOD *m;

  if (strcmp (rcvr -> __o_classname, "Class")) 
    class = __ctalk_get_object (rcvr -> __o_classname, "Class");
  else 
    class = rcvr;

  for (m = class -> class_methods; m; m = m -> next) {
    if (m -> cfunc == fn)
      return m;
    if (!m -> next)
      break;
  }
  
  if (class -> __o_superclass)
    return __ctalkGetClassMethodByFn (class -> __o_superclass, fn, warn);
  else
    return NULL;
}

METHOD *__ctalkFindInstanceMethodByName (OBJECT **rcvr, 
					 const char *name, int warn) {

  int i;
  METHOD *m = NULL;

  if ((m = __ctalkGetInstanceMethodByName (*rcvr, name, warn)) == NULL) {
    for (i = __ctalkGetReceiverPtr () + 1; !m && (i <= MAXARGS); i++) {
      if ((m = 
	   __ctalkGetInstanceMethodByName (__ctalk_receivers_at (i), 
					   name, 
					   warn))
	  != NULL) {
	*rcvr = __ctalk_receivers_at (i);
      }
    }
  }

  if (warn && !m)
    _warning
      ("__ctalkFindInstanceMethodByName: %s: %s (Class %s) does not understand %s.\n",
       __argvFileName (), (*rcvr) -> __o_name, (*rcvr) -> __o_classname, name);

  return m;
}

METHOD *__ctalkFindInstanceMethodByFn (OBJECT **rcvr, OBJECT *(*fn)(void), 
				       int warn) {

  int i;
  METHOD *m = NULL;

  if ((m = __ctalkGetInstanceMethodByFn (*rcvr, fn, warn)) == NULL) {
    for (i = __ctalkGetReceiverPtr () + 1; !m && (i <= MAXARGS); i++) {
      if ((m = 
	   __ctalkGetInstanceMethodByFn (__ctalk_receivers_at (i), fn, warn))
	  != NULL) {
	*rcvr = __ctalk_receivers_at (i);
      }
    }
  }

  if (warn && !m)
    _warning
      ("__ctalkFindInstanceMethodByFn: %s: %s (Class %s): undefined method.\n",
       __argvFileName (), (*rcvr) -> __o_name, (*rcvr) -> __o_classname);

  return m;
}

METHOD *__ctalkFindClassMethodByName (OBJECT **rcvr, 
				      const char *name, int warn) {

  int i;
  METHOD *m = NULL;

  if ((m = __ctalkGetClassMethodByName (*rcvr, name, warn)) == NULL) {
    for (i = __ctalkGetReceiverPtr () + 1; !m && (i <= MAXARGS); i++) {
      if ((m = 
	   __ctalkGetClassMethodByName (__ctalk_receivers_at (i), name, warn))
	  != NULL) {
	*rcvr = __ctalk_receivers_at (i);
      }
    }
  }

  if (warn && !m)
    _warning
      ("__ctalkFindClassMethodByName: %s: %s (Class %s) does not understand %s.\n",
       __argvFileName (), (*rcvr) -> __o_name, (*rcvr) -> __o_classname, name);

  return m;
}

METHOD *__ctalkFindClassMethodByFn (OBJECT **rcvr, OBJECT *(*fn)(void), 
				    int warn) {

  int i;
  METHOD *m = NULL;

  if ((m = __ctalkGetClassMethodByFn (*rcvr, fn, warn)) == NULL) {
    for (i = __ctalkGetReceiverPtr () + 1; !m && (i <= MAXARGS); i++) {
      if ((m = 
	   __ctalkGetClassMethodByFn (__ctalk_receivers_at (i), fn, warn))
	  != NULL) {
	*rcvr = __ctalk_receivers_at (i);
      }
    }
  }

  if (warn && !m)
    _warning
      ("__ctalkFindClassMethodByFn: %s: %s (Class %s): undefined method.\n",
       __argvFileName (), (*rcvr) -> __o_name, (*rcvr) -> __o_classname);

  return m;
}

METHOD *__ctalkFindMethodByFn (OBJECT **rcvr_p, OBJECT *(*fn)(void),int warn) {

  METHOD *m = NULL;

  if (((m = __ctalkFindInstanceMethodByFn (rcvr_p, fn, warn)) == NULL) &&
      ((m = __ctalkFindClassMethodByFn (rcvr_p, fn, warn)) == NULL))
    if (warn)
   _warning ("__ctalkFindMethodByFn: Object %s (Class %s) unknown method.\n",
	     (*rcvr_p) -> __o_name, (*rcvr_p) -> __o_classname);
  return m;
}

METHOD *__ctalkFindMethodByName (OBJECT **rcvr_p, const char *name, int warn) {

  METHOD *m = NULL;

  if (((m = __ctalkFindInstanceMethodByName (rcvr_p, name, warn)) == NULL) &&
      ((m = __ctalkFindClassMethodByName (rcvr_p, name, warn)) == NULL))
    if (warn)
_warning ("__ctalkFindMethodByName: Object %s (Class %s) does not understand method %s.\n",
  (*rcvr_p) -> __o_name, (*rcvr_p) -> __o_classname, name);

return m;
}

/* The arguments are stored in the method only. 
   The prototype allows us to add an arbitrary number 
   of arguments to the function call.
   Note that at this time, _ctalk_define_method () does
   not use the primitive method prototype (*func)(OBJECT **args).
*/
void __ctalk_primitive_method (char *receiver_name, char *name, int attrs) {

  OBJECT *receiver = NULL,  /* Avoid warnings. */
    *receiver_orig = NULL, 
    *result_obj;
  METHOD *method;
  int i;
  OBJECT *(*cfunc)();
  OBJECT *arg;

  if (attrs & METHOD_SUPER_ATTR) {
    receiver_orig = __ctalk_get_object (receiver_name, "Class");
    receiver = 
      __ctalk_get_object (__ctalk_get_object (receiver_name, "Class") -> __o_superclassname, "Class");
    __super_move_args (receiver_name, name);
  } else {
    receiver = __ctalk_get_object (receiver_name, "Class");
  }
  __ctalk_receiver_push (receiver);
  method = __ctalkGetInstanceMethodByName (receiver, name, TRUE);
  method -> arg_frame_top = __ctalkGetArgPtr () + 1;
  cfunc = method -> cfunc;
  result_obj = (OBJECT *)(cfunc) (method -> args);
  __ctalk_receiver_pop ();
  /* Don't free the args here, because they're now objects in 
     the dictionay or class dictionary. */
  for (i = 0; i < method -> n_params; i++) {
    if (method -> args[i] && IS_OBJECT (method -> args[i])) {
      __ctalkDeleteObjectInternal (method -> args[i]);
      method -> args[i] = NULL;
    }
    arg = __ctalk_arg_pop ();
  }
  method -> n_args = 0;

  if (attrs & METHOD_SUPER_ATTR) {
    /*
     *  The result object is a member of the receiver's superclass.
     *  For now, a simple fixup of the class and superclass should
     *  be sufficient.
     */
    strcpy (result_obj -> __o_classname, 
	    receiver_orig->__o_class->__o_name);
    strcpy (result_obj -> __o_superclassname, receiver_orig -> __o_superclassname);
  }
}

int __ctalkDefineInstanceMethod (char *classname, char *name, 
			     OBJECT *(*cfunc)(), int required_args) {
  METHOD *tm;
  OBJECT *class;
  
  if ((class = __ctalkGetClass (classname)) == NULL)
    _error ("__ctalk_define_method: %s: Undefined class %s", 
	    rtinfo.source_file, classname);

  if (class -> instance_methods == NULL) {
    class -> instance_methods = (METHOD *)__xalloc (sizeof (struct _method));
    strcpy (class -> instance_methods -> sig, "METHOD");
    strcpy (class -> instance_methods -> name, name);
    class->instance_methods-> n_params = required_args;
    class->instance_methods-> n_args = 0;
    class->instance_methods-> cfunc = cfunc;
    class->instance_methods-> rcvr_class_obj = class;
  } else {
    for (tm = class -> instance_methods; tm -> next; tm = tm -> next)
      ;
    tm -> next = (METHOD *)__xalloc (sizeof (struct _method));
    strcpy (tm -> next -> sig, "METHOD");
    strcpy (tm -> next -> name, name);
    tm -> next -> n_params = required_args;
    tm -> next -> rcvr_class_obj = class;
    tm -> next -> n_args = 0;
    tm -> next -> cfunc = cfunc;
    tm -> next -> prev = tm;
  }
  return SUCCESS;
}

int __ctalkDefineClassMethod (char *classname, char *name, 
			     OBJECT *(*cfunc)(), int required_args) {
  static METHOD *m;
  METHOD *tm;
  OBJECT *class;
  
  if ((class = __ctalk_get_object (classname, "Class")) == NULL)
    _error ("__ctalk_define_method: %s: Undefined class %s", 
	    rtinfo.source_file, classname);

  m = (METHOD *)__xalloc (sizeof (struct _method));

  strcpy (m -> sig, "METHOD");
  strcpy (m -> name, name);
  m -> n_params = required_args;
  m -> n_args = 0;
  m -> cfunc = cfunc;

  if (class -> class_methods == NULL) {
    class -> class_methods = m;
  } else {
    for (tm = class -> class_methods; tm -> next; tm = tm -> next)
      ;
    tm -> next = m;
    m -> prev = tm;
  }
  return SUCCESS;
}

int __ctalkDefineTemplateMethod (char *classname, char *name, 
				 OBJECT *(*cfunc)(), int required_args,
				 int n_args) {
  OBJECT *class;
  METHOD *tm;

  __ctalkDefineClassMethod (classname, name, cfunc, required_args);

  /*
   *  method -> n_args is actually set in __rt_method_args.
   *  eval_expr only needs method -> n_params.
   */
   if ((class = __ctalk_get_object (classname, "Class")) == NULL)
     _error ("__ctalkDefineTemplateMethod: %s: Undefined class %s",
 	    rtinfo.source_file, classname);

   for (tm = class -> class_methods; tm; tm = tm -> next) {
     if (tm -> cfunc == cfunc) {
       tm -> n_args = 0;
     }
   }
  return SUCCESS;
}

/*
 *   TO DO - These functions get called every time the method
 *   is called.  Like all other method init functions, make 
 *   sure that it is called only as often as necessary.
 *
 *   TO DO - rtinfo.rcvr_class_obj should always be accurate, but 
 *   make sure that it doesn't change here.
 */

void __ctalkMethodReturnClass (char *classname) {

  METHOD *m;

  if (((m = __ctalkFindInstanceMethodByFn (&rtinfo.rcvr_class_obj, 
					   rtinfo.method_fn,
					   FALSE))
       != NULL) ||
      ((m = __ctalkFindClassMethodByFn (&rtinfo.rcvr_class_obj, 
					rtinfo.method_fn,
					FALSE)) != NULL)) {
    strcpy (m -> returnclass, classname);
  } else {
    _warning ("%s: __ctalk_methodReturnClass: undefined instance method.\n",
	      __argvFileName ());
  }
}

void __ctalkInstanceMethodInitReturnClass (char *rcvr_class, 
					   char *method_name, 
					   char *return_class) {
  OBJECT *rcvr_class_obj;
  METHOD *m;

  if ((rcvr_class_obj = __ctalkGetClass (rcvr_class)) == NULL)
    _warning ("Undefined class %s.\n", rcvr_class);

  if ((m = __ctalkFindInstanceMethodByName(OBJREF (rcvr_class_obj),
				    method_name, TRUE)) == NULL)
    _warning ("Undefined method %s (class %s).\n", 
	      method_name, rcvr_class);
  strcpy (m -> returnclass, return_class);
}

void __ctalkClassMethodInitReturnClass (char *rcvr_class, 
					   char *method_name, 
					   char *return_class) {
  OBJECT *rcvr_class_obj;
  METHOD *m;

  if ((rcvr_class_obj = __ctalkGetClass (rcvr_class)) == NULL)
    _warning ("Undefined class %s.\n", rcvr_class);

  if ((m = __ctalkFindClassMethodByName(OBJREF (rcvr_class_obj),
				    method_name, TRUE)) == NULL)
    _warning ("Undefined method %s (class %s).\n", 
	      method_name, rcvr_class);
  strcpy (m -> returnclass, return_class);
}

/*
 *  Initialize local objects during a method or function init by 
 *  deleting old objects, if any.  If we're calling from a method,
 *  the local objects are stored in the METHOD struct.  If calling
 *  from a function, they are stored in __ctalk_dictionary, where
 *  we delete all objects that are not GLOBAL_VAR in scope.
 */
int __local_object_init = FALSE;
void __ctalk_initLocalObjects (void) {

  METHOD *__m;
  OBJECT *__method_rcvr_obj;
  OBJECT *(*fn)(void);
  
  if ((__method_rcvr_obj = __ctalkRtReceiverObject ()) == NULL) {
    _warning ("__ctalk_initLocalObjects: Unknown receiver.\n");
    return;
  }

  if ((fn = __ctalkRtGetMethodFn ()) == NULL) {
    _warning ("__ctalk_initLocalObjects: Unknown method fn.\n");
    return;
  }

  if ((__m = __ctalkRtGetMethod ()) == NULL) {
    _warning ("__ctalk_initLocalObjects: Unknown method.\n");
    return;
  }
  /*
   *  Local objects must absolutely be deleted here before the
   *  method creates new local objects.
   */
  if (__m -> local_objects.vars) {
    VARENTRY *__v, *__v_prev;
    for (__v = (__m->local_objects.vars); 
	 __v && __v -> var_object && 
	   IS_OBJECT(__v -> var_object) && 
	   __v -> next; 
	 __v = __v -> next)
      ; 
    if (__v == (__m->local_objects.vars)) {
      if (__m->local_objects.vars && __m->local_objects.vars -> var_object) 
	__ctalkDeleteObject (__m->local_objects.vars -> var_object);
      delete_varentry (__m->local_objects.vars);
      (__m->local_objects.vars) = NULL;
    } else {
      while (__v != (__m->local_objects.vars)) {
	__v_prev = __v -> prev;
	if (__v && __v -> var_object)
	  __ctalkDeleteObject ( __v -> var_object);
	delete_varentry (__v);
	__v = __v_prev;
	if (!__v) break;
	__v -> next = NULL;
      }
      if (__m->local_objects.vars && __m->local_objects.vars -> var_object) { 
	__ctalkDeleteObject (__m->local_objects.vars -> var_object); 
	delete_varentry (__m->local_objects.vars); 
	__m->local_objects.vars = NULL;
      }
    }
    __local_object_init = FALSE;
  }
}

/*
 *  __ctalkInstanceMethodParam () only needs to fill in the parameters
 *  immediately after the method is defined.  n_params is already
 *  filled in.  Because arguments get evaluated and then pushed in
 *  reverse order, doing the same for the parameter definitions makes
 *  things easier to follow later.
 *
 *  TO DO - The function should not depend on __ctalk_define_method ()
 *  to set n_params.  It should be a parameter in this function also.
 */

void __ctalkInstanceMethodParam (char *rcvrClass, char *name, 
			  char *paramClass, char *paramName, int isPointer) {

  OBJECT *classObj;
  METHOD *m;
  PARAM *p;
  int i;

  if ((classObj = __ctalkGetClass (rcvrClass)) == NULL) {
    _warning ("__ctalk_methodParam: Undefined class %s.\n",
	      rcvrClass);
    return;
  }

  for (m = classObj -> instance_methods; m; m = m -> next)
    if (!strcmp (m -> name, name))
      break;

  if (!m || !IS_METHOD (m)) {
    _warning ("__ctalk_methodParam: Undefined method %s (Class %s).\n",
	      name, rcvrClass);
    return;
  }

  p = new_param ();
  strcpy (p -> class, paramClass);
  strcpy (p -> name, paramName);
  if (!strcmp (p -> name, "..."))
    m -> varargs = TRUE;
  p -> is_ptr = isPointer;

  for (i = m -> n_params - 1; ; i--) {
    if (! m -> params[i] || ! IS_PARAM (m -> params[i])) {
      m -> params[i] = p;
       return; 
    }
    if (i < 0) {
      _warning ("__ctalk_methodParam: too many parameters.\n");
       return;
    }
  }
}

/*
 *  An empty argument list is the token immediately following
 *  a method that is a comma, semicolon, closing parenthesis,
 *  or closing square brace.
 *
 *  The function also checks that the starting token of the 
 *  argument list immediately follows a method token.
 */

int _is_empty_arglist (MESSAGE_STACK messages, int arglist_start, 
		       int stack_start) {

  int m_prev_token;

  if ((m_prev_token = 
       __ctalkPrevLangMsg (messages, arglist_start, stack_start)) == ERROR)
    _error ("Parser error.");

  if ((messages[m_prev_token] -> tokentype == METHODMSGLABEL) &&
      METHOD_ARG_TERM_MSG_TYPE (messages[arglist_start]))
    return TRUE;
  else
    return FALSE;
}

/*
 *  Find the end of an argument list, recursing if necessary
 *  for functions and methods as arguments.  
 *
 *  If the argument list begins with a parenthesis, simply
 *  find the matching closing parenthesis.
 *
 *  The parameter, n_args, should come from the method, or -1 
 *  if the number of args is unknown.  
 * 
 *  Empty argument lists get handled immediately.  They generate
 *  warning messages in method_args () if method -> n_args > 0.
 */

int __ctalkArglistLimit (MESSAGE_STACK messages, int arglist_start, int n_args,
			 int stack_top, int stack_start) {

  MESSAGE *m;
  int i,
    end_list,
    n_parens,
    lookahead,
    start_token;

  if (!n_args && _is_empty_arglist (messages, arglist_start, stack_start))
    return arglist_start;

  for (i = arglist_start, n_parens = 0, end_list = -1, start_token = -1; 
       (i > stack_top) && (end_list == -1); i--) {

    m = messages[i];

    if ((m -> tokentype == NEWLINE) ||
	(m -> tokentype == WHITESPACE))
      continue;

    if (start_token == -1)
      start_token = m -> tokentype;

    switch (m -> tokentype)
      {
      case OPENPAREN:
	++n_parens;
	break;
      case CLOSEPAREN:
	/*
	 *  If the first token is a closing parenthesis, then
	 *  the argument list is empty.
	 */
	if (i == arglist_start)
	  end_list = arglist_start;

	  /* 
	   *  If the next token ends an argument, then this parenthesis
	   *  ends the argument list.  Otherwise, continue to scan forward.
	   */
	if (n_parens) {
	  --n_parens;
	  /*
	   *  If a matching closing parenthesis, then check whether
	   *  there are still tokens to the argument.
	   */
	  if (!n_parens) {
	    if ((lookahead = __ctalkNextLangMsg (messages, i, stack_top)) 
		== ERROR) {
	      _warning ("Argument syntax error.");
	      end_list = arglist_start;
	    }
	    switch (messages[lookahead] -> tokentype)
	      {
	      case CLOSEPAREN:
	      case ARGSEPARATOR:
	      case SEMICOLON:
		end_list = i;
		break;
	      }
	  }
	} else {
	  /*
	   *  If we encounter a closing parenthesis without an opening
	   *  parenthesis, then the argument list ends at the previous
	   *  token.
	   */
	  if (!n_parens)
	    end_list = i + 1;
	}
	break;
      case SEMICOLON:
	end_list = i + 1;
	break;
      default:
	break;
      }
  }

  return end_list;
}

/*
 *  Check that a set of parentheses encloses an _entire_expression_;
 *  that is, we don't cut off the first and last parentheses of 
 *  an expression like (1 + 1) - (2 + 2). 
 * 
 *  To simplify this, start_ptr must point to the first opening 
 *  parenthesis, and end_ptr must point to the last token of
 *  the expression.
 */

int _external_parens (MESSAGE_STACK messages, int start_ptr, int end_ptr) {

  int i,
    n_parens;

  for (i = start_ptr, n_parens = 0; i >= end_ptr; i--) {

    switch (messages[i] -> tokentype)
      {
      case OPENPAREN:
	++n_parens;
	break;
      case CLOSEPAREN:
	--n_parens;
	if (!n_parens && (i != end_ptr))
	  return FALSE;
	if (!n_parens && (i == end_ptr))
	  return TRUE;
	break;
      default:
	break;
      }
  }

  return FALSE;
}

OBJECT *__ctalk_methodReturn (OBJECT *rcvr, METHOD *m, OBJECT *return_obj) {
  return NULL;
}

extern OBJECT *__ctalk_classes;
extern OBJECT *__ctalk_last_class;

METHOD *__this_method (void) {
  OBJECT *(*method_fn)(void);
  OBJECT *rcvr_class_obj;
  METHOD *method;

  if ((method_fn = __ctalkRtGetMethodFn ()) != NULL) {
    /*
     *  If there is a receiver class object, then use 
     *  that to find the method.
     */
    if ((rcvr_class_obj = __ctalk_rtReceiverClassObject ()) != NULL) {
      if (((method = __ctalkFindInstanceMethodByFn (OBJREF (rcvr_class_obj), 
				   method_fn, FALSE)) == NULL) &&
	  ((method = __ctalkFindClassMethodByFn (OBJREF (rcvr_class_obj), 
						 method_fn, FALSE)) == NULL)){
	_warning ("Undefined method in this_method ().\n");
	return NULL;
      }
      return method;
    } else {
      /*
       * Otherwise, perform a search of the classes.
       */
      for (rcvr_class_obj = __ctalk_classes; rcvr_class_obj; 
	   rcvr_class_obj = rcvr_class_obj -> next) {
	for (method = rcvr_class_obj -> instance_methods; method;
	     method = method -> next) {
	  if (method -> cfunc == method_fn)
	    return method;
	}
      }
      for (rcvr_class_obj = __ctalk_classes; rcvr_class_obj; 
	   rcvr_class_obj = rcvr_class_obj -> next) {
	for (method = rcvr_class_obj -> class_methods; method;
	     method = method -> next) {
	  if (method -> cfunc == method_fn)
	    return method;
	}
      }
    }
  }
  _warning ("Undefined method in this_method ().\n");
  return NULL;
}

int __super_move_args  (char *rcvr_name, char *method_name) {

  OBJECT *rcvr_obj, *class_obj;
  METHOD *method, *superclass_method;
  int i, arg_ptr;

  if ((rcvr_obj = __ctalk_get_object (rcvr_name, NULL)) == NULL) {
    if ((rcvr_obj = __ctalk_get_object (rcvr_name, "Class")) == NULL)
      _error ("__super_move_args (): Undefined receiver.\n");
  }
  class_obj = rcvr_obj -> __o_class;

  method = __ctalkGetInstanceMethodByName (rcvr_obj, method_name, TRUE);
  superclass_method = 
    __ctalkGetInstanceMethodByName (class_obj -> __o_superclass, 
				    method_name, TRUE);


  for (i = 0, arg_ptr = __ctalkGetArgPtr () + 1; 
       i < method -> n_args; i++, arg_ptr++) {
    if (method -> arg_frame_top && (arg_ptr >= method -> arg_frame_top))
      break;
    superclass_method -> args[i] = method -> args[method -> n_args - i - 1];
    method -> args[method -> n_args - i - 1] = NULL;
    ++superclass_method -> n_args;
    --method -> n_args;
  }

  return SUCCESS;
}
