/* $Id: rt_args.c,v 1.19 2009/02/28 19:17:52 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 <string.h>
#include <stdlib.h>
#include <errno.h>
#include "object.h"
#include "message.h"
#include "ctalk.h"
#include "rtinfo.h"

extern RT_INFO rtinfo;              /* Declared in rtinfo.c. */
extern RT_INFO *__call_stack[MAXARGS+1];
extern int __call_stack_ptr;
extern int eval_status;

static OBJECT *__ctalk_argstack[MAXARGS+1];
static int __ctalk_arg_ptr = MAXARGS;

int __ctalkGetArgPtr (void) {
  return __ctalk_arg_ptr;
}

int __ctalk_arg_push (OBJECT *__o) {
  if (__ctalk_arg_ptr == 0)
    _error ("__ctalk_arg_push: stack_overflow.\n");

  __ctalk_argstack[__ctalk_arg_ptr--] = __o;
  return SUCCESS;
}

int __ctalk_arg_push_ref (OBJECT *__o) {
  if (__ctalk_arg_ptr == 0)
    _error ("__ctalk_arg_push: stack_overflow.\n");

  __refObj (OBJREF(__ctalk_argstack[__ctalk_arg_ptr--]), OBJREF(__o));
  return SUCCESS;
}

OBJECT *__ctalk_arg_pop (void) {

  OBJECT *__o;
  if (__ctalk_arg_ptr > (MAXARGS + 1))
    _error ("arg_pop: Message stack overflow, arg_ptr = %d.\n", 
	    __ctalk_arg_ptr);

  __o = __ctalk_argstack[++__ctalk_arg_ptr];
  __ctalk_argstack[__ctalk_arg_ptr] = NULL;
  return __o;
}

OBJECT *__ctalk_arg_pop_deref (void) {

  OBJECT *__o;
  if (__ctalk_arg_ptr > (MAXARGS + 1))
    _error ("arg_pop: Message stack overflow, arg_ptr = %d.\n", 
	    __ctalk_arg_ptr);

  __o = __ctalk_argstack[++__ctalk_arg_ptr];
  __ctalk_argstack[__ctalk_arg_ptr] = NULL;
  __objRefCntDec (OBJREF (__o));
  return __o;
}

extern Boolean is_constant_rcvr;
/* extern Boolean is_arg_expr; */

int __ctalk_arg (char *__recv_name, char *__method, void *__arg) {

  OBJECT *__recv_obj, *__recv_class, *arg_object = NULL;
  OBJECT *arg_p_object = NULL;
  METHOD *m;
  CVAR *c;

  /*
   *   The state, is_constant_rcvr, needs to be set 
   *   only by _constant_rcvr.  See the notes in 
   *   __eval_expr () and _constant_rcvr ().
   *   This variable should probaly go away in a 
   *   future release.
   */
  is_constant_rcvr = False;

  /*
   *   __ctalkEvalExpr () needs to know that the expression
   *   is a method argument, in case, "self," appears 
   *   in the argument.
   */
/*   is_arg_expr = True; */

  /*
   *   Find the receiver depending on whether it is a class
   *   object, a dictionary object, a method parameter, or
   *   a constant expression.
   */

  if ((__recv_class = __ctalkGetClass (__recv_name)) == NULL) {
    if ((__recv_obj = __ctalk_get_object (__recv_name, NULL))
	== NULL)
      if ((__recv_obj = __ctalk_getParamByName (__recv_name)) 
	  == NULL)
	if ((__recv_obj = __constant_rcvr (__recv_name)) 
	    == NULL) 
	  _error ("__ctalk_arg: %s: Undefined receiver %s.\n",
		  rtinfo.source_file, __recv_name);
	
    if ((__recv_class = __recv_obj->__o_class) == NULL)
      _error ("__ctalk_arg: %s: Undefined receiver %s class %s.\n",
	      rtinfo.source_file, __recv_obj -> __o_name, 
	      __recv_obj -> __o_classname);
  } else {
    __recv_obj = __recv_class;
  }

  /*
   *   If we can't find the method, then there is probably an
   *   argument mismatch.  Check for the method in the calling
   *   method's receiver class.
   */

  if ((m = __ctalkFindInstanceMethodByName (&__recv_class, __method, TRUE))
      == NULL)
    return ERROR;

  /*
   *  Check if the argument is already on the argument stack or is 
   *  in the dictionary.
   */
  if (!strncmp ((char *)__arg, "__ctalk_arg_internal", 
		strlen ("__ctalk_arg_internal"))) {
    int arg_n;
    METHOD *__calling_method;
    __calling_method = 
      __ctalkFindInstanceMethodByFn (&__recv_obj, 
			      __ctalkRtGetMethodFn (), TRUE);
    sscanf ((char *)__arg, "__ctalk_arg_internal (%d)", &arg_n);
    arg_object = __ctalk_argstack[__calling_method -> arg_frame_top + arg_n];
    __refObj (OBJREF (m -> args[m -> n_args++]), OBJREF(arg_object));
    __ctalk_arg_push_ref (arg_object);
    return SUCCESS;
  }

  if ((arg_object = __ctalk_get_object ((char *)__arg, NULL)) == NULL) {
    if ((arg_object = __ctalk_getstack ((char *)__arg)) == NULL) {

    /*
     *  Also check if the argument was declared in a method as 
     *  OBJECT *.
     */

      if ((c = get_method_arg_cvars ((char *)__arg)) != NULL) {
	CVAR *c_2;
	arg_object = cvar_object (c);
	/*
	 *  If this warning occurs, it means that a C argument is 
	 *  either not being deleted quickly enough after it is 
	 *  instantiated as an object and there are multiple C 
	 *  variables with the same name.  Variable shadowing 
	 *  should produce a warning in the interpreter, and the 
	 *  scoping needs to be checked more closely in rt_cvar.c.
	 */
	if ((c_2 = remove_method_arg_cvar (c -> name)) != c)
	  _warning ("__ctalk_args: C variable mismatch.\n");
	_delete_cvar (c);
      }
      
      /*
       *  If the arg is the result of a C function call from
       *  __ctalkC...ToObj, then __arg is an object already.
       */
      if (!arg_object) {
	if (__arg && IS_OBJECT (__arg))
	  arg_object = (OBJECT *)__arg;
      }

      if (!arg_object) {

	/* 
	 *    If we have to define an object, the primitives need the 
	 *    receiver on the stack.  
	 */
	__ctalk_receiver_push_ref (__recv_obj);
	eval_status = 0;
	arg_object = eval_expr ((char *)__arg, __recv_class, m, NULL,
				__recv_obj -> scope, TRUE);
	__ctalk_receiver_pop_deref ();
	delete_method_arg_cvars ();

	if (is_constant_rcvr == True)
	  is_constant_rcvr = False;

	if (!arg_object)
	  arg_object = null_result_obj (m, __recv_obj -> scope);

	if (arg_object -> nrefs == 0) {
	  /*
	   *  Arg was created.  Set scope to something other
	   *  than GLOBAL_VAR so the arg gets deleted 
	   *  in cleanup_args ().
	   */
	  arg_object -> scope = ARG_VAR;
	  if (arg_object -> instancevars) 
	    arg_object -> instancevars -> scope = ARG_VAR;
	}

	/* 
	 *  For constants, the argument's value should be its name,
	 *  unless it's a character string, in which case the value
	 *  should be a pointer to the buffer. 
	 *
	 *  We shouldn't set the object value if the argument refers
	 *  to a class object, or if the object the result of an
	 *  expression.  In that case, eval_expr () returns an object
	 *  with the name, "value", and possibly without instance
	 *  variables.
	 */
	if (!IS_CLASS_OBJECT(arg_object)) {
	  if (strcmp (arg_object -> __o_name, "value") && 
	      arg_object -> instancevars != NULL)
	    __ctalkSetObjectValue (arg_object, (char *)__arg);
	} 
      } /* if (!arg_object) */
    } /* if ((arg_object = __ctalk_getstack ((char *)__arg)) == NULL) */
  } /*if (((arg_object = __ctalk_get_object ((char *)__arg, NULL)) == NULL) */
  if (eval_status & (EVAL_STATUS_TERMINAL_TOK|EVAL_STATUS_INSTANCE_VAR)) {
    arg_p_object = arg_object;
  } else {
    arg_p_object = 
      ((arg_object -> __o_p_obj != NULL) ? 
       arg_object -> __o_p_obj : arg_object);
  }
  if (arg_p_object -> nrefs == 0)
    __ctalkSetObjectScope (arg_p_object, ARG_VAR);
  __refObj (OBJREF (m -> args[m -> n_args++]), OBJREF(arg_p_object));
  __ctalk_arg_push_ref (arg_p_object);
/*   is_arg_expr = False; */
  eval_status = 0;
  return SUCCESS;
}

int __ctalkCreateArg (char *__recv_name, char *__method, void *__arg) {

  OBJECT *__recv_class, *arg_object = NULL;
  METHOD *m;
  CVAR *c;

  /* See the comments in __ctalk_arg (). */

  /*
   *  Caution! The only place this function is used so far is 
   *  is for creating local method objects, so it assumes that
   *  __recv_name is the name of a class.  Not tested yet with
   *  random receivers from __ctalk_get_object ().
   */
  if ((__recv_class = __ctalkGetClass (__recv_name)) == NULL) {
    OBJECT *__r;
    if ((__r = __ctalk_get_object (__recv_name, NULL)) != NULL) {
      __recv_class = __r -> __o_class;
    } else {
      _error ("__ctalkCreateArg: %s: Undefined receiver class %s.\n",
	      rtinfo.source_file, __recv_name);
    }
  }
  if ((m = __ctalkFindInstanceMethodByName (&__recv_class, __method, TRUE))
      == NULL)
    return ERROR;

  if (!strncmp ((char *)__arg, "__ctalk_arg_internal", 
		strlen ("__ctalk_arg_internal"))) {
    int arg_n;
    METHOD *__calling_method;
    __calling_method = 
      __ctalkFindInstanceMethodByFn (&__recv_class, 
			      __ctalkRtGetMethodFn (), TRUE);
    sscanf ((char *)__arg, "__ctalk_arg_internal (%d)", &arg_n);
    arg_object = __ctalk_argstack[__calling_method -> arg_frame_top + arg_n];
    __refObj (OBJREF (m -> args[m -> n_args++]), OBJREF(arg_object));
    __ctalk_arg_push_ref (arg_object);
    return SUCCESS;
  }

  if ((c = get_method_arg_cvars ((char *)__arg)) != NULL) {
    CVAR *c_2;
    arg_object = cvar_object (c);
    if ((c_2 = remove_method_arg_cvar (c -> name)) != c)
      _warning ("__ctalk_args: C variable mismatch.\n");
    _delete_cvar (c);
  }
      
  if (!arg_object) {
    if (__arg && IS_OBJECT (__arg))
      arg_object = (OBJECT *)__arg;
  }

  if (!arg_object) {

    arg_object = 
      __ctalkCreateObjectInit ((char *)__arg,
			       __recv_name,
			       ((__recv_class -> __o_superclass) ? 
				__recv_class->__o_superclass->__o_name : 
				NULL),
			       ARG_VAR,
			       (char *)__arg);
  } /* if (!arg_object) */
  __refObj (OBJREF (m -> args[m -> n_args++]), OBJREF(arg_object));
  __ctalk_arg_push_ref (arg_object);

  return SUCCESS;
}

OBJECT *__ctalk_getstack (void *p) {
  int i;
  for (i = __ctalk_arg_ptr + 1; i <= MAXARGS; i++)
    if (p == __ctalk_argstack[i])
      return p;
  return NULL;
}

/*
 *   Method args start from 0, which is at 
 *   __ctalk_argstack[__call_stack[__call_stack_ptr+1]->_arg_frame_top].
 */
OBJECT *__ctalk_arg_internal (int n) {
  return __ctalk_argstack[__call_stack[__call_stack_ptr+1]->_arg_frame_top+n];
}

/* 
 *  Called once per arg from within a class library or source module.
 *  Global objects should be deleted explicitly by the application
 *  program.
 *
 *  Returns the number of reference counts of the argument object.
 */

int __ctalk_arg_cleanup (void) {
  OBJECT *arg_object;
  int arg_nrefs;
  arg_object = __ctalk_arg_pop_deref ();
  arg_nrefs = arg_object -> nrefs;
  if (arg_object -> nrefs == 0) {
    if (arg_object -> scope != GLOBAL_VAR)
      __ctalkDeleteObjectInternal (arg_object);
    return 0;
  } else {
    if ((arg_object -> scope == ARG_VAR) &&
	(arg_object -> nrefs == 1)) {
      /*
       *  The arg is going to be dereffed to 0 anyway.
       */
      __ctalkDeleteObject(arg_object);
      return 0;
    } else {
      if (arg_object -> scope != GLOBAL_VAR) {
	__ctalkDeleteObjectInternal (arg_object);
	--arg_nrefs; if (arg_nrefs < 0) arg_nrefs = 0;
      }
      if (!arg_object -> __o_p_obj || !IS_OBJECT(arg_object -> __o_p_obj)) {
	if (object_is_decrementable(arg_object, NULL, NULL))
	  --arg_nrefs; if (arg_nrefs < 0) arg_nrefs = 0;
	if (object_is_deletable(arg_object, NULL, NULL))
	  arg_nrefs = 0;
	_cleanup_temporary_objects (arg_object, NULL, NULL);
      }
    }
  }
  return arg_nrefs;
}

int is_arg (OBJECT *__o) {
  int i;
  for (i = __ctalk_arg_ptr + 1; i <= MAXARGS; i++)
    if (__o == __ctalk_argstack[i])
      return TRUE;
  return FALSE;
}

/*
 *  Return the stack index of the first possible receiver
 *  of this method, or -1 there is not immediate or previous
 *  receiver.  Called only by __rt_method_args, so the only
 *  stack should be e_messages.  The backtrack is only
 *  slightly greedy.  It checks whether the method token is 
 *  valid for the primary receiver of the complete expression,
 *  then for the first token of the argument subexpression only.
 *
 *  TO DO - No compound receivers in this version.
 */

static int __method_backtrack (char *method_name,
			       MESSAGE_STACK messages,
			       int method_idx) {
  EXPR_PARSER *p;
  int stack_end, arg_rcvr_idx, primary_method_idx, primary_rcvr_idx;
  OBJECT *primary_rcvr_obj, *arg_rcvr_obj;
  METHOD *arg_method, *primary_method;

  p = __current_expr_parser ();
  stack_end = __get_expr_parser_ptr ();
  
  if (__ctalk_isMethod (M_NAME(messages[method_idx]),
			messages,
			method_idx,
			p -> msg_frame_top,
 			stack_end)) {
    if (((arg_rcvr_idx = 
	 __ctalkPrevLangMsg (messages, method_idx, p->msg_frame_top)) 
	!= ERROR) &&
	((primary_method_idx = 
	  __ctalkPrevLangMsg (messages, arg_rcvr_idx, p->msg_frame_top))
	 != ERROR) &&
	((primary_rcvr_idx = 
	  __ctalkPrevLangMsg (messages, primary_method_idx, p->msg_frame_top))
	 != ERROR)) {
      if (__ctalk_isMethod (M_NAME(messages[primary_method_idx]),
			     messages,
			     primary_method_idx,
			     p -> msg_frame_top,
			     stack_end)) {
	primary_rcvr_obj = M_OBJ(messages[primary_rcvr_idx]);
	arg_rcvr_obj = M_OBJ(messages[arg_rcvr_idx]);
	if (__ctalkFindInstanceMethodByName (&primary_rcvr_obj,
					       M_NAME(messages[method_idx]),
					     FALSE)) {
	  /* Incomplete expression. */
	  if (!primary_rcvr_obj || !IS_OBJECT(primary_rcvr_obj) ||
	      !arg_rcvr_obj || !IS_OBJECT(arg_rcvr_obj))
	    return arg_rcvr_idx;

	  if ((((arg_method = 
	       __ctalkFindInstanceMethodByName (&arg_rcvr_obj,
						M_NAME(messages[method_idx]),
						FALSE)) != NULL) ||
	       ((arg_method = 
		  __ctalkFindClassMethodByName (&arg_rcvr_obj,
						M_NAME(messages[method_idx]),
						FALSE)) != NULL)) &&
	      (((primary_method =
		__ctalkFindInstanceMethodByName (&primary_rcvr_obj,
					 M_NAME(messages[primary_method_idx]),
						 FALSE)) != NULL) ||
	       ((primary_method =
		 __ctalkFindClassMethodByName (&primary_rcvr_obj,
				       M_NAME(messages[primary_method_idx]),
					       FALSE)) != NULL))) {
	    if (!strcmp (primary_method -> params[0] -> class,
			 arg_method -> returnclass)) {
	      return arg_rcvr_idx;
	    } else {
	      /*
	       *  This should be enough to recognize Collection subclasses.
	       */
	      if (((!strcmp (arg_method -> returnclass, "Any")) &&
		  __ctalkIsSubClassOf (arg_rcvr_obj -> __o_classname,
				       arg_method -> 
				       rcvr_class_obj -> __o_name)) ||
		  !strcmp (arg_rcvr_obj -> __o_classname,
			   arg_method -> returnclass) || 
		  __ctalkIsSubClassOf (arg_rcvr_obj -> __o_classname,
				       arg_method -> returnclass)) {
		return arg_rcvr_idx;
	      } else {
		if (!strcmp (primary_method -> returnclass,
			     arg_method -> returnclass) ||
		    __ctalkFindMethodByName (OBJREF(primary_rcvr_obj),
					     M_NAME(messages[method_idx]),
					     FALSE)) {
		  return primary_rcvr_idx;
		} else {
		  return arg_rcvr_idx; /* Incomplete expression. */
		}
	      }
	    }
	  } else {
	    return arg_rcvr_idx;  /* Incomplete expression. */
	  }
	}
	/*
	 *  Handle cases where there is only an argument method found.
	 */
	if (((__ctalkFindInstanceMethodByName (&arg_rcvr_obj,
					      M_NAME(messages[method_idx]),
					      FALSE)) != NULL) ||
	    ((__ctalkFindClassMethodByName (&arg_rcvr_obj,
					    M_NAME(messages[method_idx]),
					    FALSE)) != NULL)) {
	  /*
	   *  The method can match the receiver of the argument
	   *  subexpression.
	   *
	   *  TO DO - Check whether superceded by the cases above.
	   */
	  return arg_rcvr_idx;
	}
      } else {
	return arg_rcvr_idx;
      }
    }
  } else {
    /*
     *  Next arg token is not a method.
     */
    return ERROR;
  }
  return ERROR;
}

/*
 * Define a single token evaluation as a macro,
 * so we don't have to define rt_argstrs[][], 
 * for another function.
 */
#define EVAL_SINGLE_ARG_TOK { rt_argstrs[0].arg = \
		    strdup (messages[rt_argstrs[0].start_idx] -> name); \
		  rt_argstrs[0].end_idx = rt_argstrs[0].start_idx; \
		  arg_val_obj = __ctalk_arg_expr (messages, method_msg_ptr,\
						  rt_argstrs[0].arg); \
                  } \

int __rt_method_args(METHOD *method, MESSAGE_STACK messages, 
		     int stack_start, int stack_end, int method_msg_ptr){

  int arglist_start,
    arglist_end,
    i,
    j,
    open_paren_ptr,
    close_paren_ptr,
    last_arg_tok_idx,
    next_arg_tok_idx;
  Boolean paren_delim;
  char argbuf[MAXMSG];
  OBJECT *rcvr_class_obj,
    *arg_val_obj;
  MESSAGE *m_arg, 
    *m_method;
  struct _rt_argstrs {
    char *arg;
    int start_idx,
      end_idx;
  } rt_argstrs[MAXARGS] = { { 0, } };
  int rt_argstrptr;

  /*
   *  If we're at the end of the expression stack, then the
   *  end of the argument list is the method token index.
   */
  if ((arglist_start = 
       __ctalkNextLangMsg (messages, method_msg_ptr, stack_end)) == ERROR)
    return method_msg_ptr;

   arglist_end = __rt_arglist_limit (messages, stack_start, stack_end, 
				     method_msg_ptr, arglist_start, 
				     method -> n_params);

  /*
   *  Handle empty argument lists directly.
   *  
   *  This is the case where there are no further tokens after the method.
   */
  if ((arglist_start == arglist_end) && 
      _is_empty_arglist (messages, arglist_start, stack_start)) {
    if (method -> n_params)
      _warning ("Method %s: Wrong number of arguments\n.", method -> name);
    return arglist_start; 
  }

  /*
   *  Handle cases where the argument is already evaluated.
   */
  if (messages[arglist_start] -> value_obj && 
      (messages[arglist_start] -> value_obj == 
       messages[arglist_end] -> value_obj) &&
      messages[arglist_start] -> evaled) {
    __refObj (OBJREF (method -> args[method -> n_args++]), 
	      OBJREF(messages[arglist_end]->value_obj));
    __ctalk_arg_push_ref (messages[arglist_end]->value_obj);
    return arglist_end;
  }
  if ((messages[arglist_end] -> attrs & 
       (OBJ_IS_INSTANCE_VAR|OBJ_IS_CLASS_VAR)) &&
      (messages[arglist_end] -> receiver_msg == messages[arglist_start])) {
    __refObj (OBJREF (method -> args[method -> n_args++]), 
	      OBJREF(messages[arglist_end]->receiver_msg->value_obj));
    __ctalk_arg_push_ref (messages[arglist_end]->receiver_msg->value_obj);
    return arglist_end;
  }

  /*
   *  If the method does not require parameters, then the 
   *  next message must also be a method, so check its
   *  return class and determine if the next message
   *  is also a method message.
   *
   *  __arglist_limit (), above, has already checked for 
   *  the cases where the next message is an argument
   *  list terminator.
   */

  if (!method -> n_params) {
    OBJECT *return_class_obj;
    int m_next_msg;
    METHOD *arg_method;
    if ((return_class_obj = 
	 __ctalkGetClass (method -> returnclass)) == NULL) {
      if (!strcmp (method -> returnclass, "Any")) {
	/*
	 *  For now, if we don't know the return class
	 *  of the method, then use the receiver class,
	 *  which is probably a Collection, and we 
	 *  should be able to use a method dispatcher
	 *  later on.
	 *
	 *  TODO - This is a good place for a stack walkback.
	 */
	int __prev_idx;
	if ((__prev_idx = __ctalkPrevLangMsg (messages,
					      method_msg_ptr,
					      stack_start))
	    != ERROR) {
	  return_class_obj = messages[__prev_idx] -> obj -> __o_class;
	} else {
	  _warning ("Undefined return class %s (method %s).\n",
		    method -> returnclass, method -> name);
	  return method_msg_ptr;
	}
      } else {
	_warning ("Undefined return class %s (method %s).\n",
		  method -> returnclass, method -> name);
	return method_msg_ptr;
      }
    }

    if ((m_next_msg = __ctalkNextLangMsg (messages, method_msg_ptr, 
					  stack_end)) == ERROR)
      _error ("Parser error.\n");
      
    if ((arg_method = 
	 __ctalkGetInstanceMethodByName (return_class_obj, 
					 messages[m_next_msg] -> name,
					 TRUE))
	== NULL) {

      if (!strcmp (return_class_obj -> __o_name, "Character")) {
	if ((return_class_obj = 
	     __ctalkGetClass ("Integer")) != NULL) {
	  if ((arg_method = 
	       __ctalkGetInstanceMethodByName (return_class_obj, 
					       messages[m_next_msg] -> name, 
					       TRUE)) == NULL) {

	    if (!strcmp (return_class_obj -> __o_name, "Integer")) {
	      if ((return_class_obj = 
		   __ctalkGetClass ("LongInteger")) == NULL) {
		if ((arg_method = 
		     __ctalkGetInstanceMethodByName (return_class_obj, 
					     messages[m_next_msg] -> name,
						     TRUE)) == NULL) {

		  _warning ("Undefined return class %s (method %s).\n",
			return_class_obj -> __o_name, method -> name);
		  return method_msg_ptr;
		}
	      }
	      _warning ("Undefined argument method to %s.\n", method -> name);
	    }
	  }
	}
      }
    }
    /*
     *  Case where two consecutive methods follow a receiver.  The
     *  argument is actually another method.
     */
    if (M_TOK(messages[m_next_msg]) == LABEL)
      messages[m_next_msg] -> tokentype = METHODMSGLABEL;
    return method_msg_ptr;
  }

  /*
   *  Adjust the start and end indexes to exclude parentheses.
   * 
   *  However, we also have to check that the parentheses
   *  enclose the entire expression, so we don't also 
   *  exclude parentheses in expressions like (1 + 1) - (2 + 2).
   *
   *  The function also checks for parentheses for each argument,
   *  below.
   */

  if ((messages[arglist_start] -> tokentype == OPENPAREN) &&
      (messages[arglist_end] -> tokentype == CLOSEPAREN) &&
      _external_parens (messages, arglist_start, arglist_end)) {
    paren_delim = True;

    ++messages[arglist_start] -> evaled;
    ++messages[arglist_start] -> output;
    ++messages[arglist_end] -> evaled;
    ++messages[arglist_end] -> output;
    open_paren_ptr = arglist_start;
    close_paren_ptr = arglist_end;
    arglist_start = __ctalkNextLangMsg (messages, arglist_start, stack_end);
    arglist_end = __ctalkPrevLangMsg (messages, arglist_end, stack_start);

    /*
     *  Check for a typecast expression.
     */
    if (__rt_is_typecast (messages, arglist_start, arglist_end)) {
      if (typecast_is_pointer (messages, arglist_start, arglist_end)) {
	typecast_ptr_expr (messages, arglist_start, arglist_end);
      } else {
	typecast_value_expr (messages, arglist_start, arglist_end);
      }
      if ((arg_val_obj = M_VALUE_OBJ(messages[arglist_start])) != NULL) {
	__refObj (OBJREF (method -> args[method -> n_args++]), 
		  OBJREF(arg_val_obj));
	__ctalk_arg_push_ref (arg_val_obj);
      }
      return arglist_end;
    }

  } else {
    paren_delim = False;
    open_paren_ptr = close_paren_ptr = -1;
  }

  rt_argstrs[0].start_idx = arglist_start;
  rt_argstrs[0].end_idx = arglist_end;


  /*
   *  If the method requires only one argument, and does not
   *  accept a variable number of arguments, evaluate the 
   *  entire expression as a single argument.
   */
  if (!method -> varargs && (method -> n_params == 1)) {

    m_method = messages[method_msg_ptr];
    rcvr_class_obj = m_method -> receiver_obj -> __o_class;

    /*
     *  Here we have to evaluate the argument to determine if 
     *  it is a single token or an expression.
     */

    if (messages[rt_argstrs[0].start_idx] -> tokentype == OPENPAREN) {
      /*
       *  First check for parentheses.
       */
      if ((rt_argstrs[0].end_idx = 
	__ctalkMatchParen (messages, rt_argstrs[0].start_idx, 
			   stack_end)) == ERROR) {
	_warning ("__rt_method_args: mismatched parentheses.");
      } else {
	/*
	 * If there's more expression following a closing parenthesis,
	 * adjust the arglist limit.
	 */
	if ((paren_delim == False) &&
	    (arglist_end < rt_argstrs[0].end_idx)) {
	  rt_argstrs[0].end_idx = arglist_end;
	}
      }
      rt_argstrs[0].arg = 
	collect_tokens (messages, 
			rt_argstrs[0].start_idx,
			rt_argstrs[0].end_idx);
      arg_val_obj = __ctalk_arg_expr (messages, method_msg_ptr, 
				      rt_argstrs[0].arg);
      goto arg_reevaled_1;
    } else {
      /* Argument is not enclosed in parentheses. */
      if (rt_argstrs[0].start_idx != rt_argstrs[0].end_idx) {
	/*
	 *  Check for an aggregate C variable arg here, too.
	 */
	if (messages[rt_argstrs[0].start_idx] -> attrs & 
	    CVAR_AGGREGATE_TOK) {
	  arg_val_obj = M_VALUE_OBJ(messages[rt_argstrs[0].start_idx]);
	  goto arg_reevaled_1;
	}
  	if ((next_arg_tok_idx =
  	     __ctalkNextLangMsg (messages, rt_argstrs[0].start_idx,
  				 stack_end)) != ERROR) {
 	  int first_rcvr_idx;
 	  if ((first_rcvr_idx = 
 	       __method_backtrack (M_NAME(messages[next_arg_tok_idx]),
 				   messages,
 				   next_arg_tok_idx)) != ERROR) {
 	    if (first_rcvr_idx ==
 		__ctalkPrevLangMsg (messages,
 				    next_arg_tok_idx,
 				    __current_expr_parser () -> msg_frame_top))
 	      {
		int first_rcvr_idx_2, method_idx_2;
		/*  Check the next token for a method. */
		if ((method_idx_2 = 
		     __ctalkNextLangMsg (messages, rt_argstrs[0].start_idx,
					 stack_end)) != ERROR) {
		  if (__ctalk_isMethod (M_NAME(messages[method_idx_2]),
					messages, method_idx_2, 
				__current_expr_parser () -> msg_frame_top,
					stack_end)) {
		    first_rcvr_idx_2 = 
		      __method_backtrack (M_NAME(messages[method_idx_2]),
					  messages,
					  method_idx_2);
		    if (first_rcvr_idx_2 == 
			__ctalkPrevLangMsg (messages, 
					    method_idx_2,
			    __current_expr_parser () -> msg_frame_top)) {
		      rt_argstrs[0].arg =
			collect_tokens (messages,
					rt_argstrs[0].start_idx,
					rt_argstrs[0].end_idx);
		      arg_val_obj = __ctalk_arg_expr (messages, method_msg_ptr,
						      rt_argstrs[0].arg);
		      goto arg_reevaled_1;
		    } else {
 		      EVAL_SINGLE_ARG_TOK
		      goto arg_reevaled_1;
		    }
		  } else {
		    /*
		     *  Next token is not a method.
		     *  TO DO - check for instance and
		     *  class variable messages.
		     */
 		    EVAL_SINGLE_ARG_TOK
		    goto arg_reevaled_1;
		  }
		} else {
		  /* No next token. */
		  EVAL_SINGLE_ARG_TOK
		  goto arg_reevaled_1;
		}
 	    } else {
	      /*  Method matches only the primary receiver. */
 	      EVAL_SINGLE_ARG_TOK
 	      goto arg_reevaled_1;
 	    }
 	  } else {
	    /* None of the above. */
 	    EVAL_SINGLE_ARG_TOK
	      goto arg_reevaled_1;
 	  }
  	} else {
  	  rt_argstrs[0].arg =
  	    collect_tokens (messages,
  			    rt_argstrs[0].start_idx,
  			    rt_argstrs[0].end_idx);
  	  arg_val_obj = __ctalk_arg_expr (messages, method_msg_ptr,
  					  rt_argstrs[0].arg);
  	  goto arg_reevaled_1;
  	}
      } else {
  	rt_argstrs[0].end_idx = rt_argstrs[0].start_idx;
	if (messages[rt_argstrs[0].start_idx] -> obj &&
	    IS_OBJECT (M_VALUE_OBJ (messages[rt_argstrs[0].start_idx]))) {
	  arg_val_obj = M_VALUE_OBJ (messages[rt_argstrs[0].start_idx]);
	} else {
	  rt_argstrs[0].arg = 
	    strdup (messages[rt_argstrs[0].start_idx] -> name);
	  arg_val_obj = __ctalkEvalExpr (rt_argstrs[0].arg);
	}
	goto arg_reevaled_1;
      }
    }

  arg_reevaled_1:

    __refObj (OBJREF (method -> args[method -> n_args++]), 
	      OBJREF(arg_val_obj));
    __ctalk_arg_push_ref (arg_val_obj);

    last_arg_tok_idx = rt_argstrs[0].end_idx;

    free (rt_argstrs[0].arg);
    rt_argstrs[0].arg = NULL;
    rt_argstrs[0].start_idx = rt_argstrs[0].end_idx = 0;

    return last_arg_tok_idx;
  }


  /*
   *  Multiple arguments.
   */

  for (i = arglist_start, rt_argstrptr = 0, *argbuf = 0; i >= arglist_end; i--) {

    m_arg = messages[i];

    switch (m_arg -> tokentype)
      {
      case SEMICOLON:
      case ARGSEPARATOR:
	rt_argstrs[rt_argstrptr].arg = 
	  strdup (trim_leading_whitespace (argbuf));
	rt_argstrs[rt_argstrptr++].end_idx = 
	  __ctalkPrevLangMsg (messages, i, stack_start);
	/*
	 *  Set the start index of the next argument here.
	 */
	rt_argstrs[rt_argstrptr].start_idx = 
	  __ctalkNextLangMsg (messages, i, stack_end) ;
	*argbuf = 0;
	++m_arg -> evaled;
	++m_arg -> output;
	break;
      default:
	strcat (argbuf, m_arg -> name);
	break;
      }

    if (i == arglist_end) {
      rt_argstrs[rt_argstrptr].arg = 
	strdup (trim_leading_whitespace (argbuf));
      rt_argstrs[rt_argstrptr++].end_idx = arglist_end;
    }
  }

  m_method = messages[method_msg_ptr];
  rcvr_class_obj = m_method -> receiver_obj -> __o_class;

  for (i = 0; i < rt_argstrptr; i++) {
    if ((rt_argstrs[i].start_idx != rt_argstrs[i].end_idx) ||
	!(messages[rt_argstrs[i].start_idx] -> attrs & 
	  TOK_OBJ_IS_CREATED_PARAM)) {
      arg_val_obj = __ctalk_arg_expr (messages, method_msg_ptr, 
				      rt_argstrs[i].arg);
      for (j = rt_argstrs[i].start_idx; j >= rt_argstrs[i].end_idx; j--) {
	if (M_VALUE_OBJ (messages[j]) != arg_val_obj) {
	  messages[j] -> value_obj = arg_val_obj;
	}
      }
      __refObj (OBJREF (method -> args[method -> n_args++]), 
		OBJREF(arg_val_obj));
      __ctalk_arg_push_ref (arg_val_obj);
      /*
       *  If an instance variable of a publicly visible object, 
       *  then the entire object needs to have the same reference
       *  count.
       */
      if (arg_val_obj -> __o_p_obj && 
	  (arg_val_obj -> __o_p_obj -> __o_varentry)) {
	__objRefCntSet (OBJREF(arg_val_obj -> __o_p_obj), 
			arg_val_obj -> nrefs);
      }
    } else {
      arg_val_obj = messages[rt_argstrs[i].start_idx] -> obj;
      if (M_VALUE_OBJ (messages[rt_argstrs[i].start_idx]) != arg_val_obj) {
	messages[rt_argstrs[i].start_idx] -> value_obj = arg_val_obj;
      }
      __refObj (OBJREF (method -> args[method -> n_args++]),
 		OBJREF(arg_val_obj));
      __ctalk_arg_push_ref (arg_val_obj);
    }
  }

  last_arg_tok_idx = rt_argstrs[rt_argstrptr-1].end_idx;

  while (--rt_argstrptr >= 0) {
    free (rt_argstrs[rt_argstrptr].arg);
    rt_argstrs[rt_argstrptr].arg = NULL;
    rt_argstrs[rt_argstrptr].start_idx = 
      rt_argstrs[rt_argstrptr].end_idx = 0;
  }

  return last_arg_tok_idx;
}

int __rt_arglist_limit (MESSAGE_STACK messages, int stack_start, int stack_end,
			int primary_method_idx,
			int arglist_start, int n_args) {

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

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

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

     /*
      *  With the expression in its own frame, if we reach the end of the
      *  stack, return the last message.
      */
     if (i == stack_end + 1) {
       return 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_end)) == ERROR) {
	       _warning ("Argument syntax error.");
	       end_list = arglist_start;
	     } else {
	       if (IS_C_OP_TOKEN_NOEVAL(M_TOK(messages[lookahead]))) {
		 /*
		  *  Check for an individual argument wrapped in 
		  *  parentheses.  If there are further arguments,
		  *  then we won't have reached the end of the argument
		  *  list.
		  */
		 if ((M_TOK(messages[lookahead]) == ARGSEPARATOR) &&
		     !n_parens) {
		   if (n_arg_separators == (n_args - 1)) {
		     end_list = i;
		   }
		 } else {
		   end_list = i;
		 }
	       }
	     }
	   }
	 } 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;
       case ARGSEPARATOR:
	 ++n_arg_separators;
	 break;
       case PLUS:
       case MINUS:
       case MULT:
       case DIVIDE:
       case ASL:
       case ASR:
       case BIT_AND:
       case BIT_OR:
       case BIT_XOR:
       case BOOLEAN_EQ:
       case GT:
       case LT:
       case GE:
       case LE:
       case INEQUALITY:
       case BOOLEAN_AND:
       case BOOLEAN_OR:
       case MODULUS:
       case CONDITIONAL:     /* No-op for now. */
       case SIZEOF:
       case INCREMENT:
       case DECREMENT:
       case EQ:
       case ASR_ASSIGN:
       case ASL_ASSIGN:
       case PLUS_ASSIGN:
       case MINUS_ASSIGN:
       case MULT_ASSIGN:
       case DIV_ASSIGN:
       case BIT_AND_ASSIGN:
       case BIT_OR_ASSIGN:
       case BIT_XOR_ASSIGN:
	 /*
	  *  Any operator not within a subexpression should
	  *  end the argument list.  eval_expr () deals with
	  *  specific operator precedences.
	  *
	  *  The function must re-lex the primary method token in 
	  *  case a caller has changed it to METHODMSGLABEL.
	  *
	  *  Does not handle a case where an operator follows a 
	  *  method label.  __rt_method_args adjusts the argument
	  *  list limit using the primary method.
	  */
	 if (!n_parens) {
	   long long int idx = 0ll;
	   m_tok_orig = new_message ();
	   lexical (M_NAME(messages[primary_method_idx]),
		    &idx, m_tok_orig);
	   if (IS_C_OP(M_TOK(m_tok_orig))) {
	     end_list = 
	       __ctalkPrevLangMsg (messages, i, stack_start);
	   }
	   delete_message (m_tok_orig);
	 }
	 /*
	  *  Any operator not within a subexpression should
	  *  end the argument list.  eval_expr () deals with
	  *  specific operator precedences.
	  */
       default:
	 break;
       }
   }

   return end_list;
}

/*
 *   Call eval_expr () from within an argument.  The receiver
 *   is the object immediately before the method whose arguments
 *   we're evaluating, and the method argument to eval_expr ()
 *   is NULL.
 */

OBJECT *__ctalk_arg_expr (MESSAGE_STACK messages, int method_ptr, 
			  char *expr) {
  int rcvr_ptr,
    stack_start;
  OBJECT *rcvr_obj,
    *rcvr_class_obj,
    *result_obj;

  stack_start = __rt_get_stack_top (messages);

  if ((rcvr_ptr = 
       __ctalkPrevLangMsg (messages, method_ptr, stack_start)) == ERROR)
    _error ("Parser error.");

  rcvr_obj = M_VALUE_OBJ (messages[rcvr_ptr]);
  if (!rcvr_obj || !IS_OBJECT (rcvr_obj))
    _error ("Parser error.");

  if (IS_CLASS_OBJECT(rcvr_obj)) {
    rcvr_class_obj = rcvr_obj;
  } else {
    rcvr_class_obj = rcvr_obj -> __o_class;
  }
  if (!rcvr_class_obj || !IS_OBJECT (rcvr_class_obj))
    _error ("Parser error.");
    
  if (is_printf_fmt(expr)) {
    result_obj =  
      __ctalkCreateObjectInit (expr, STRING_CLASSNAME,
			       STRING_SUPERCLASSNAME,
			       rcvr_obj -> scope,
			       expr);
  } else {
    __ctalk_receiver_push_ref (rcvr_obj);

    result_obj = eval_expr (expr, rcvr_class_obj, NULL, NULL, 
			    rcvr_obj -> scope, FALSE);

    __ctalk_receiver_pop_deref ();
  }

  return result_obj;
}
