/* $Id: arg.c,v 1.8 2008/11/06 20:54:36 kiesling Exp $ */

/*
  This file is part of ctalk.
  Copyright © 2005-2008 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 program is free software; you can redistribute it and/or modify
  it under the terms of the GNU General Public License as published by
  the Free Software Foundation; either version 2 of the License, or
  (at your option) any later version.

  This program 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 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 <errno.h>
#include "object.h"
#include "message.h"
#include "ctalk.h"
#include "lex.h"
#include "typeof.h"

extern I_PASS interpreter_pass;    /* Declared in main.c.               */

extern int arg_has_leading_unary;  /* Declared in method.c.             */

extern int ctrlblk_pred;           /* Declared in control.c.            */

extern METHOD *new_methods[MAXARGS+1]; /* Declared in lib/rtnwmthd.c.   */
extern int new_method_ptr;

extern HASHTAB declared_global_variables;
extern HASHTAB declared_typedefs;
extern HASHTAB declared_functions;

extern OBJECT *rcvr_class_obj;     /* Declared in primitives.c.        */

/*
 *  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 paren_expr (MESSAGE_STACK messages, int open_paren_idx) {

  int _m_paren;

  if ((_m_paren = match_paren (messages, open_paren_idx, 
			       get_stack_top(messages)))
      == ERROR) {
    __ctalkExceptionInternal (messages[open_paren_idx], 
			      mismatched_paren_x, NULL);
    return ERROR;
  }

  return _m_paren;
}

int method_arg_limit (MESSAGE_STACK messages, int arg_start) {

  int n_parens, n_subscripts, i, stack_end_idx, lookahead;
  int arg_arglist_limit;

  n_parens = n_subscripts = i = stack_end_idx = 0;

  switch (M_TOK(messages[arg_start]))
    {
    case OPENPAREN:
      if ((i = paren_expr (messages, arg_start)) == ERROR)
	return ERROR;
      if ((lookahead = nextlangmsg (messages, i)) == ERROR)
	return ERROR;
      /*
       *  If we don't find an argument terminator immediately
       *  after the expression's closing parenthesis, scan
       *  forward until we do.
       */
      switch (M_TOK(messages[lookahead]))
	{
	case CLOSEPAREN:
	case SEMICOLON:
	case ARGSEPARATOR:
	  return i;
	  /* break; */   /* Not reached. */
	default:
	  /*
	   *  The function also needs to handle cases like 
	   *    a = (1 + 1) + (2 + 2); 
	   *    a = (1 + 1) + (2 + 2) + (3 + 3);
	   *  etc., here.
	   */
	  if (IS_C_BINARY_MATH_OP(M_TOK(messages[lookahead]))) {
	    if (!n_parens) {
	      if ((lookahead = nextlangmsg (messages, lookahead)) == ERROR)
		return ERROR;
	      return method_arg_limit (messages, lookahead);
	    }
	  } else {
	    for (; i > stack_end_idx; i--) {
	      if ((lookahead = nextlangmsg (messages, i)) == ERROR)
		return ERROR;
	      switch (M_TOK(messages[lookahead]))
		{
		case CLOSEPAREN:
		case SEMICOLON:
		case ARGSEPARATOR:
		  return i;
		  /* break; */  /* Not reached. */
		}
	    }
	  }
	}
      break;
    default:
      /*
       *  Arguments that don't begin with a parenthesis.
       */
      stack_end_idx = get_stack_top (messages);
      for (i = arg_start; i > stack_end_idx; i--) {
	if (M_ISSPACE(messages[i]))
	  continue;
	if ((lookahead = nextlangmsg (messages, i)) == ERROR) {
	  if (EXPR_CHECK) {
	    return i;
	  } else {
	    return ERROR;
	  }
	}
	switch (M_TOK(messages[i]))
	  {
	  case LABEL:
	    if ((arg_arglist_limit = resolve_arg2 (messages, i)) != 0) {
	      /*
	       *  Reset i and lookahead to end of an internal method
	       *  arglist.
	       */
	      i = arg_arglist_limit;
	      if ((lookahead = nextlangmsg (messages, i)) == ERROR) {
		if (EXPR_CHECK) {
		  return i;
		} else {
		  return ERROR;
		}
	      }
	    }
	    break;
	  case OPENPAREN:
	    ++n_parens;
	    break;
	  case ARRAYOPEN:
	    ++n_subscripts;
	    break;
	  case ARRAYCLOSE:
	    if (--n_subscripts < 0) return i;
	    break;
	  case CLOSEPAREN:
	    if (--n_parens < 0) return i;
	    break;
	  }
	switch (M_TOK(messages[lookahead])) 
	  {
	  case ARGSEPARATOR:
	    if (!n_parens) return i;
	    break;
	  case CLOSEPAREN:
	    if (!n_parens) return i;
	    break;
	  case ARRAYCLOSE:
	    if (!n_subscripts) return i;
	    break;
	  case SEMICOLON:
	    return i;
	    /* 	    break; */  /* Not reached. */
	  }
      }
      break;
    }
  return ERROR;
}

int method_arglist_limit (MESSAGE_STACK messages, int arglist_start,
			  int n_params, int varargs) {

  int arg_start, arg_end, lookahead, n_args;

  arg_start = arglist_start;
  n_args = 0;

  if (METHOD_ARG_TERM_MSG_TYPE(messages[arglist_start]))
    return arglist_start;

  while (TRUE) {
    if ((arg_end = method_arg_limit (messages, arg_start)) == ERROR)
      return ERROR;
    if ((lookahead = nextlangmsg (messages, arg_end)) == ERROR) {
      if (EXPR_CHECK) {
	return arg_end;
      } else {
	__ctalkExceptionInternal (messages[arglist_start], 
				  parse_error_x, NULL);
	return ERROR;
      }
    }

    switch (M_TOK(messages[lookahead]))
      {
      case CLOSEPAREN:
      case SEMICOLON:
      case ARRAYCLOSE:
	return arg_end;
	/* 	break; */  /* Not reached.*/
      case ARGSEPARATOR:
	if ((n_args < (n_params - 1)) || varargs) {
	  ++n_args;
	  if ((arg_start = nextlangmsg (messages, lookahead)) == ERROR) {
	    __ctalkExceptionInternal (messages[arglist_start], 
				      parse_error_x, NULL);
	    return ERROR;
	  }
	} else {
	  return arg_end;
	}
	break;
      }
  }

  return ERROR;
}

int global_constructor_arg (METHOD *method, int method_msg_ptr) {

  MESSAGE *m_method,
    *m_rcvr, 
    *m_arg;
  METHOD *m_actual;
  OBJECT *rcvr;
  int scope,
    arg_ptr;

  if (strcmp (method -> name, "new") ||
      ((scope = parser_frame_scope ()) != GLOBAL_VAR))
    return ERROR;

  m_method = message_stack_at (method_msg_ptr);
  m_rcvr = m_method -> receiver_msg;
  rcvr = m_method -> receiver_obj;

  m_actual = method_replaces_primitive (method, rcvr);

  if ((arg_ptr = nextlangmsg (message_stack (), method_msg_ptr))
      == ERROR)
    _error ("global_constructor_arg (): Argument for %s not found.",
	    m_method -> name);

  m_arg = message_stack_at (arg_ptr);

  m_actual -> args[0] = create_object (rcvr -> __o_name, m_arg -> name);
  strcpy (m_actual -> args[0] -> __o_superclassname, rcvr -> __o_superclassname);

  ++m_actual -> n_args;

  if (m_arg -> obj) {_warning ("Object needs to be released in global_constructor_arg.\n"); }
  m_arg -> obj = m_actual -> args[0];

  return SUCCESS;
}

/*
 *  Return the index of a function argument label matching arg_object
 *  if it is within a parenthesized argument list after start_idx, 
 *  FALSE otherwise.
 */
int fn_is_arg (OBJECT *arg_object, MESSAGE_STACK messages, 
	       int arglist_start_idx) {

  int i;
  int arglist_end_idx;
  MESSAGE *m_tok;

  arglist_end_idx = method_arglist_limit (messages, arglist_start_idx, 0, 
					  FALSE);

  for (i = arglist_start_idx; i > arglist_end_idx; i--) {
    m_tok = messages[i];
    if (M_ISSPACE(m_tok)) continue;
    if (M_TOK(m_tok) == LABEL) {
      if (!strstr (M_NAME(m_tok), arg_object -> __o_name))
	return i;
    }
  }

  return FALSE;
}

int fn_arg_expression_call = FALSE;

OBJECT *fn_arg_expression (OBJECT *rcvr_class, METHOD *method, 
			 MESSAGE_STACK messages, int fn_idx) {
  int n_args, arg_start, arg_end, n_th_arg;
  char *args[MAXARGS];
  char expr_buf[MAXMSG];
  char namebuf[MAXLABEL];
  OBJECT *arg_object;
  CVAR *arg_cvar = NULL;
  CFUNC *fn_cfunc = NULL;

  if ((arg_start = scanforward (messages, fn_idx, 
				get_stack_top (messages),
				OPENPAREN)) == ERROR) {
    warning (messages[fn_idx], "Function syntax error.");
    return NULL;
  }
  if ((arg_end = match_paren (messages, arg_start, get_stack_top (messages)))
      == ERROR) {
    warning (messages[fn_idx], "Function syntax error.");
    return NULL;
  }

  split_args (messages, arg_start, arg_end, args, &n_args);

  /*
   *  For now, transfer a function CVAR to a CFUNC, which is 
   *  quicker to find.
   *  Superceded ... 
   */
  *expr_buf = 0;
  for (n_th_arg = 0; n_th_arg < n_args; n_th_arg++) {

    if (!fn_cfunc) {

      if (strchr (args[n_th_arg], '[')) {
	strcpy (namebuf, subscript_var_basename(args[n_th_arg]));
      } else {
	if (strchr (args[n_th_arg], '.')) {
	  if (_lextype(args[n_th_arg]) == DOUBLE_T) {
	    strcpy (namebuf, args[n_th_arg]);
	  } else {
	    strcpy (namebuf, deref_var_basename(args[n_th_arg]));
	  }
	} else {
	  if (strstr (args[n_th_arg], "->")) {
	    strcpy (namebuf, deref_var_basename(args[n_th_arg]));
	  } else {
	    strcpy (namebuf, args[n_th_arg]);
	  }
	}
      }

      if (((arg_cvar = get_local_var (namebuf)) != NULL) ||
	  ((arg_cvar = get_global_var (namebuf)) != NULL)) {
	strcat (expr_buf, args[n_th_arg]);
	if (n_th_arg < (n_args - 1))
	  strcat (expr_buf, ", ");
	continue;
      }

      /* Look for method parameters. */
      if (interpreter_pass == method_pass) {
	METHOD *__n_method;
	int __n_th_param;
	char __argbuf[MAXMSG];
	PARAM *p;
	__n_method = new_methods[new_method_ptr+1];
	for (__n_th_param = 0; __n_th_param < __n_method -> n_params; 
	     __n_th_param++) {
	  if (!strcmp (__n_method -> params[__n_th_param] -> name, 
		       args[n_th_arg])) {
	    p = __n_method -> params[__n_method->n_params-__n_th_param-1];
 	    SNPRINTF (__argbuf, MAXMSG, "%s (%d)", METHOD_ARG_ACCESSOR_FN,
		      __n_method->n_params-__n_th_param-1);
	    strcat (expr_buf, fmt_rt_return (__argbuf, p -> class, TRUE));
	  }
	}
	continue;
      }
    }

    arg_object = eval_arg (method, rcvr_class, args[n_th_arg], fn_idx);

    if (arg_object) {
      OBJECT *__tmp_arg_object;
      if (!strcmp (arg_object -> __o_classname, "Expr")) {
	/*
	 *  Within a function, an arg can be used unchanged, 
	 *  unless it uses, "self" as the receiver, in which
	 *  case the arg needs a __ctalkEvalExpr call.
	 * 
	 *  TO DO - Signal to the eval_arg call above that it 
	 *  is not necessary to register the variable as a 
	 *  method arg.
	 */
	if (strstr (arg_object -> __o_name, "self")) {
	  strcat (expr_buf, fmt_eval_expr (arg_object -> __o_name));
	  delete_object (arg_object);
	} else {
	  strcat (expr_buf, args[n_th_arg]);
	  delete_object (arg_object);
	}
      } else {
	/* 
	 *  A CFunction object. 
	 */
	if (!strcmp (arg_object -> __o_classname, "CFunction")) {
	  strcat (expr_buf, arg_object -> __o_name);
	} else {
	  /*
	   *  A genuine, declared object.
	   */
	  if (((__tmp_arg_object = get_object (arg_object -> __o_name,
					       arg_object -> __o_classname))
	       != NULL) && 
	      (__tmp_arg_object == arg_object)) {
	    strcat (expr_buf, 
		    obj_arg_to_c_expr (messages, fn_idx, arg_object, n_args));
	  } else {
	    /*
	     *  Handle "self" as a receiver in an argument.
	     */
	    if (strstr (arg_object -> __o_name, "self")) {
	      strcat (expr_buf, 
		      obj_arg_to_c_expr (messages, fn_idx, arg_object, 
					 n_args));
	      delete_object (arg_object);
	    } else {
	      if (get_local_var (arg_object -> __o_name) || 
		  global_var_is_declared (arg_object -> __o_name)) {
		/*
		 *  Eval_arg gives C variables an "Expr" class, so
		 *  they are handled above.
		 */
		delete_object (arg_object);
	      }  else {
		/*
		 *  Anything else - make sure that the argument is
		 *  added to expr_buf.
		 */
		strcat (expr_buf, arg_object -> __o_name);
		delete_object (arg_object);
		arg_object = NULL;
	      }
	    }
	  }
	}
      }
    }
    if (n_th_arg < (n_args - 1))
      strcat (expr_buf, ", ");
  }

  for (n_th_arg = 0; n_th_arg < n_args; n_th_arg++)
    free (args[n_th_arg]);

  arg_object = create_object (CFUNCTION_CLASSNAME, expr_buf);
  return arg_object;
}

int split_args (MESSAGE_STACK messages, int start_paren, int end_paren,
		char **args, int *n_args) {

  int i, arg_start_idx, arg_end_idx, n_parens;
  char *t_buf;

  for (i = start_paren, *n_args = 0, n_parens = 0, arg_start_idx = -1; 
       i >= end_paren; i--) {
    
    if (M_ISSPACE (messages[i])) continue;

    switch (M_TOK(messages[i]))
      {
      case OPENPAREN:
	++n_parens;
	if (n_parens == 1) {
	  if ((arg_start_idx = nextlangmsg (messages, i)) == ERROR) {
	    _warning ("Parser error.\n");
	    return ERROR;
	  } else {
	    i = arg_start_idx + 1;
	  }
	}
	break;
      case CLOSEPAREN:
	--n_parens;
	if (n_parens == 0) {
	  if ((arg_end_idx = prevlangmsg (messages, i)) == ERROR) {
	    _warning ("Parser error.\n");
	    return ERROR;
	  } else {
	    t_buf = collect_tokens (messages, arg_start_idx, arg_end_idx);
	    args[(*n_args)++] = strdup (t_buf);
	    free (t_buf);
	  }
	}
	break;
      case ARGSEPARATOR:
	if (n_parens == 1) {
	  if ((arg_end_idx = prevlangmsg (messages, i)) == ERROR) {
	    _warning ("Parser error.\n");
	    return ERROR;
	  } else {
	    t_buf = collect_tokens (messages, arg_start_idx, arg_end_idx);
	    args[(*n_args)++] = strdup (t_buf);
	    free (t_buf);
	  }
	  if ((arg_start_idx = nextlangmsg (messages, i)) == ERROR) {
	    _warning ("Parser error.\n");
	    return ERROR;
	  } else {
	    i = arg_start_idx + 1;
	  }
	}
	break;
      }
  }
  return SUCCESS;
}

char *obj_arg_to_c_expr (MESSAGE_STACK messages, int fn_idx, 
			 OBJECT *arg_object, int n_args) {
  CFUNC *fn;
  CVAR *c, *param_c = NULL;  /* Avoid a warning. */
  int n_th_param;
  char t_buf[MAXMSG];
  char cls_buf[MAXLABEL];
  static char t_buf2[MAXMSG];

  SNPRINTF (t_buf, MAXMSG, "%s(\"%s\")", 
	    EVAL_EXPR_FN, arg_object -> __o_name);
  fn = get_function (M_NAME(messages[fn_idx]));
  c = get_global_var (M_NAME(messages[fn_idx]));
  if (fn) {
    for (n_th_param = 0, param_c = fn -> params; 
	 (n_th_param < (n_args - 1)); n_th_param++) {
      param_c = param_c -> next;
    }
  } else {
    if (c) {
      for (n_th_param = 0, param_c = c -> params; 
	   (n_th_param < (n_args - 1)) && param_c; n_th_param++) {
	param_c = param_c -> next;
      }
    } else {
      warning (messages[fn_idx], "Could not find function definition %s.",
		     M_NAME(messages[fn_idx]));
    }
  }

  if (param_c) {
    strcpy (cls_buf, basic_class_from_cvar (param_c, 0));
    strcpy (t_buf2, fmt_rt_return (t_buf, cls_buf, TRUE));
  } else {
    if (is_translatable_basic_class (arg_object -> __o_classname)) {
      /*
       *  TO DO - Provide an optional warning if we can't find the
       *  type of the argument.
       */
      strcpy (t_buf2, fmt_rt_return (t_buf, arg_object->__o_classname,TRUE));
    } else {
      /*
       *  TO DO! - If evaluating a control block predicate, see if we can
       *  avoid creating a function expression altogether, by avoiding
       *  the call to fn_arg_obj () from eval_arg () in method.c.  It should
       *  be okay to avoid evaluating the args completely if we are simply
       *  going to write a template expression.
       */
      if (!ctrlblk_pred && !EXPR_CHECK) {
	warning (messages[fn_idx], 
		 "Could not find %s parameter definition. Type defaulting to int.", 
		 arg_object -> __o_name);
      }
      strcpy (t_buf2, fmt_rt_return (t_buf, INTEGER_CLASSNAME, TRUE));
    }
  }

  return t_buf2;
}

int arg_is_constant (MESSAGE_STACK messages, int start_idx, int end_idx) {

  int i;
  MESSAGE *m_tok;

  for (i = start_idx; i > end_idx; i--) {

    m_tok = messages[i];

    if (M_ISSPACE(m_tok)) continue;

    switch (M_TOK(m_tok))
      {
      case LABEL:
	if (!is_c_keyword(M_NAME(m_tok)))
	  return FALSE;
	break;
      default:
	break;
      }
  }

  return TRUE;
}

char *fmt_c_fn_obj_args_expr (MESSAGE_STACK messages, int fn_idx) {
  static char expr_buf[MAXMSG];
  char arg_buf[MAXMSG];
  char arg_class[MAXLABEL];
  int arglist_start_ptr,
    arglist_end_ptr;
  char *args[MAXARGS];
  int n_args, n_th_arg;
  CVAR *fn_param;
  CFUNC *fn_cfunc;

  if ((arglist_start_ptr = nextlangmsg (messages, fn_idx)) == ERROR)
    error (messages[fn_idx], "fmt_c_fn_obj_args_expr: Parser error.");
  if ((arglist_end_ptr = match_paren (messages, arglist_start_ptr, 
				      get_stack_top (messages))) == ERROR)
    error (messages[fn_idx], "fmt_c_fn_obj_args_expr: Parser error.");
  
  split_args (messages, arglist_start_ptr, arglist_end_ptr, args, &n_args);

  if ((fn_cfunc = get_function (M_NAME(messages[fn_idx]))) == NULL) {
    warning (messages[fn_idx], "Undefined function %s.\n", 
	       M_NAME(messages[fn_idx]));
  }
  SNPRINTF (expr_buf, MAXMSG, "%s (", M_NAME(messages[fn_idx]));
  for (n_th_arg = 0, fn_param = fn_cfunc -> params; 
       (n_th_arg < n_args);
       n_th_arg++, fn_param = fn_param -> next) {
    if (!fn_param) {
      _warning ("fmt_c_fn_obj_args_expr: wrong number of arguments.\n");
      break;
    }

    strcpy (arg_class, basic_class_from_cvar (fn_param, 0));
    strcpy (arg_buf, fmt_rt_return (fmt_eval_expr (args[n_th_arg]), 
				    arg_class, TRUE));
    strcat (expr_buf, arg_buf);
    if (n_th_arg < (n_args - 1))
      strcat (expr_buf, ",");
    /*
     *  basic_class_from_cvar should print a warning if the
     *  parameter CVAR is is invalid.
     */
    if (!fn_param || !IS_CVAR(fn_param))
      break;
  }
  strcat (expr_buf, ")");

  for (n_th_arg = 0; n_th_arg < n_args; n_th_arg++)
    free (args[n_th_arg]);
  return expr_buf;
}

int resolve_arg2 (MESSAGE_STACK messages, int idx) {

  OBJECT *result_object;
  int prev_msg_idx;
  int m_next_idx;
  int arg_method_arglist_limit;
  METHOD *arg_method;

  /*
   *  Look for a receiver.
   */
  if ((result_object = class_object_search (M_NAME(messages[idx]))) != NULL) {
    if (!M_OBJ(messages[idx])) {
      messages[idx] -> obj = result_object;
    } else {
      messages[idx] -> value_obj = result_object;
    }
    return SUCCESS;
  }
  
  if (!strcmp (M_NAME(messages[idx]), "self")) {
    if (interpreter_pass != method_pass) {
      __ctalkExceptionInternal (messages[idx], self_without_receiver_x, NULL);
      return ERROR;
    }
    if (!M_OBJ(messages[idx])) {
      messages[idx] -> obj = rcvr_class_obj;
    } else {
      messages[idx] -> value_obj = rcvr_class_obj;
    }
    return SUCCESS;
  }

  if ((result_object = get_local_object (M_NAME(messages[idx]), NULL)) 
      != NULL) {
    if (!M_OBJ(messages[idx])) {
      messages[idx] -> obj = result_object;
    } else {
      messages[idx] -> value_obj = result_object;
    }
    return SUCCESS;
  }

  /*
   *  Expression check pass.
   */
  if ((prev_msg_idx = prevlangmsg (messages, idx)) == ERROR)
    return SUCCESS;

  /*
   *  TO DO - This does not check for constant receivers.
   */
  if (M_TOK(messages[prev_msg_idx]) != LABEL)
    return SUCCESS;

  if (M_VALUE_OBJ(messages[prev_msg_idx])) {
    if ((arg_method = get_instance_method (messages[idx], 
					   M_VALUE_OBJ(messages[prev_msg_idx]),
					   M_NAME(messages[idx]),
					   FALSE)) != NULL) {
      m_next_idx = nextlangmsg (messages, idx);
      if (METHOD_ARG_TERM_MSG_TYPE(messages[m_next_idx]))
	return SUCCESS;
      arg_method_arglist_limit = 
	method_arglist_limit (messages, m_next_idx, 
			      arg_method -> n_params, arg_method -> varargs);
      return arg_method_arglist_limit;
    }
  }

  return 0;
}
