/* $Id: fn_tmpl.c,v 1.7 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.
*/

/*
 *  Functions that handle C functions in method arguments.
 *  If there is an assignment, 
 *  Some cases:
 *
 *  1.  myInt = strlen (myString)    -->
 *      myInt = CFunction cStrlen (myString);
 *  
 *  2.  ((myInt = strlen (myString)) == length) --> 
 *      ((myInt = CFunction cStrlen (myString)) == length)
 *
 *  3.  (strlen (myString) != EOF)   -->
 *      (CFunction cStrlen (myString) != EOF)
 *  
 *  The first two cases, where the libc function is the argument
 *  to a method, are handled by clib_fn_expr (), and get the 
 *  function object from eval_arg () and other functions in 
 *  method.c. 
 *
 *  The third case, where the function appears in a C context, 
 *  is handled by clib_fn_rt_expr (), and it creates its own
 *  expression object for the function template.
 *
 *  TO DO - If other contexts for libc functions arise.
 *
 *  1. Much of the code is duplicated, and should be 
 *     factored out.
 *  2. Add a check for the context that the libc function appears
 *     in, both in conditional predicates and normal statements,
 *     and handle both of these cases with the same functions.
 */

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <errno.h>
#include <unistd.h>
#include <ctype.h>
#include "object.h"
#include "message.h"
#include "ctalk.h"
#include "list.h"
#include "fntmpl.h"

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

static Boolean expr_class_defined = False; /* CFunction class and superclass */
static Boolean cfunction_class_defined = False; /* definitions added to      */
                                           /* __ctalk_init ().               */
extern int nolinemarker_opt,               /* Declared in main.c.            */
  nolibinc_opt;

extern RT_EXPR_CLASS rt_expr_class;        /* Declared in control.c.         */

static MESSAGE *tmpl_messages[N_MESSAGES + 1];
static int tmpl_messageptr;

static LIST *pending_templates,
  *templates_ptr;

static LIST *cached_fns,   /* The names of the C functions that have already */
  *cached_fn_ptr;          /* been registered.                               */

static char ctpp_ofn_template[FILENAME_MAX];

static struct _template_info {
  char fn_name[MAXLABEL];
  int n_params;
  int n_args;
  PARAM *params[MAXARGS];
  OBJECT *args[MAXARGS];
} template_info;

MESSAGE_STACK tmpl_message_stack (void) {
  return tmpl_messages;
}

int tmpl_message_push (MESSAGE *m) {
  if (tmpl_messageptr == 0) {
    _error ("Tmpl_message_push: stack_overflow.");
    return ERROR;
  }

  if (!m)
    _error ("Tmpl_message_push: null pointer, tmpl_messageptr = %d.", 
	    tmpl_messageptr);

  tmpl_messages[tmpl_messageptr] = m;

#ifdef STACK_TRACE
  debug ("Tmpl_message_push %d. %s.", tmpl_messageptr, 
	 (tmpl_messages[tmpl_messageptr] && 
	  IS_MESSAGE (tmpl_messages[tmpl_messageptr])) ?
	 tmpl_messages[tmpl_messageptr] -> name : "(null)"); 
#endif

  --tmpl_messageptr;

  return tmpl_messageptr + 1;
}

MESSAGE *tmpl_message_pop (void) {

  MESSAGE *m;

  if (tmpl_messageptr == N_MESSAGES) {
    return (MESSAGE *)NULL;
  }

  if (tmpl_messageptr > N_MESSAGES)
    _error ("Tmpl_message_pop: Message stack overflow, tmpl_messageptr = %d.", 
		  tmpl_messageptr);

  m = tmpl_messages[++tmpl_messageptr];
  tmpl_messages[tmpl_messageptr] = NULL;

#ifdef STACK_TRACE
  debug ("Tmpl_message_pop %d. %s.", tmpl_messageptr, m -> name);
#endif

  if (!m || !IS_MESSAGE (m))
    _error ("tmpl_message_pop (): Bad message, stack index %d.", 
	    tmpl_messageptr);
  else
    return m;
}

int get_tmpl_messageptr (void) {
  return tmpl_messageptr;
}

MESSAGE *tmpl_message_stack_at (int idx) {
  return tmpl_messages[idx];
}

/*
 *  Create a run-time expression for a function template.  The
 *  idx parameter should point to the libc function we need to
 *  replace.  
 *  
 *  If the function is on the right hand side of a method, the 
 *  CFunction method registration is handled earlier in 
 *  ctrlblk_pred_rt_expr (), with a CFunction object created by
 *  eval_arg ().
 * 
 *  If, however, the function is called individually; e.g., 
 *
 *    while (scanf ("%c", c) != EOF) ... 
 *
 *  then we create a temporary CFunction object on the fly, and 
 *  we don't need to find a receiver class, because the template
 *  method's class is CFunction.
 *
 *  NOTE: At the moment it is only necessary to replace the message 
 *  name.  If the parser needs to make another pass, it will be necessary
 *  to return a value object, or re-tokenize the expression.
 */
char *clib_fn_rt_expr (MESSAGE_STACK messages, int idx) {

  static char buf[MAXMSG];
  char fn_name[MAXLABEL], method_name[MAXLABEL], exprbuf[MAXLABEL];
  char *c99name, *cache_entry, *preprocessed_template, *c_template, *expr_buf;
  FN_TMPL *template;
  OBJECT *fn_object;

  if ((c99name = c99_name (messages[idx] -> name, TRUE)) == NULL)
    return NULL;

  if ((cache_entry = fn_tmpl_is_cached (M_NAME(messages[idx]))) == NULL) {
    init_c_fn_class ();
    if ((template = cache_template (c99name)) == NULL) {
      _warning ("Unregistered function template in clib_fn_rt_expr ().\n");
      return NULL;
    }
    if ((preprocessed_template = preprocess_template (template)) == NULL) {
      error (messages[idx], "Error preprocessing template for %s.",
	     template -> name);
    } else {
      /*
       *  Find template fn name and initialize parameters.  
       *  This is similar to the code below. 
       */
      int tmpl_size, token_type, param_start_idx, param_end_idx;
      long long token_start, tmpl_idx;
      MESSAGE *m;

      tmpl_size = strlen (preprocessed_template);
      tmpl_idx = 0;

      if ((m = new_message()) == NULL)
	_error ("clib_fn_rt_expr (): Invalid new_message.\n");

      while (tmpl_idx < tmpl_size) {

	token_start = tmpl_idx;

	token_type = lexical (preprocessed_template, &tmpl_idx, m);
	if (token_type == LABEL) {
	  if (is_c_function_declaration (&preprocessed_template[token_start])) {
	    template -> tmpl_fn_name = strdup (m -> name);
	    break;
	  }
	}
      }
      delete_message (m);

      strcpy (template_info.fn_name, M_NAME(messages[idx]));

      /*
       *  Fill in the template_info params.  Again, this is 
       *  similar to the code below, except that we create our
       *  own function object - it does not come from eval_arg ().
       */
      template_params (preprocessed_template, template -> tmpl_fn_name);
      if ((param_start_idx = nextlangmsg (messages, idx)) == ERROR)
	error (messages[idx], "Parser error.");
      if (M_TOK(messages[param_start_idx]) != OPENPAREN)
	error (messages[param_start_idx], "Parser error.");
      if ((param_end_idx = match_paren (messages, param_start_idx, 
					get_stack_top (messages))) == ERROR)
	error (messages[param_start_idx], "Parser error.");
      expr_buf = collect_tokens (messages, idx, param_end_idx);
      fn_object = create_object (CFUNCTION_CLASSNAME, expr_buf);
      strcpy (fn_object -> __o_superclassname, CFUNCTION_SUPERCLASSNAME);
      fn_object -> __o_superclass = 
	get_class_object(CFUNCTION_SUPERCLASSNAME);
      __ctalkSetObjectValue (fn_object, expr_buf);
      free (expr_buf);
      c_tmpl_fn_args (messages, fn_object, idx);
      c_template = insert_c_methd_return_type (preprocessed_template, 
					       template -> tmpl_fn_name);
      free (preprocessed_template);

      strcpy (exprbuf, fmt_template_rt_expr (template));

      generate_fn_template_init (template -> tmpl_fn_name, "Object");
      cache_fn_tmpl_name (template_info.fn_name, template -> tmpl_fn_name);
      buffer_fn_template (c_template);

      strcpy (method_name, template -> tmpl_fn_name);

      delete_object (fn_object);
      free (c_template);
    }
  } else {
    sscanf (cache_entry, TEMPLATE_CACHE_FMT, fn_name, method_name);
  }

  sprintf (buf, " %s %s ", CFUNCTION_CLASSNAME, method_name);

  return buf;
}

/*
 *  Create a ctalk expression for a function template.  
 *  
 */

char *clib_fn_expr (MESSAGE_STACK messages, int idx, int fn_idx, 
		    OBJECT *fn_obj) {
  char *c99name,
    *preprocessed_template,
    *c_template,
    *cache_entry;
  static char exprbuf[MAXMSG];
  char fn_name[MAXLABEL], method_name[MAXLABEL];
  long long tmpl_idx,
    tmpl_size;
  FN_TMPL *template;
  CVAR *c;
  CFUNC *fn;
  int n;

  if ((c99name = c99_name (messages[fn_idx] -> name, FALSE)) == NULL) {
    if (((c = get_global_var (M_NAME(messages[fn_idx]))) == NULL) &&
	((fn = get_function (M_NAME(messages[fn_idx]))) == NULL)) {
      warning (messages[fn_idx], "Undefined function %s.",
		M_NAME(messages[fn_idx]));
      return NULL;
    } else {
      return user_fn_template (messages, fn_idx);
    }
  }
  
  if ((template = get_template (c99name)) == NULL) {
    if ((template = cache_template (c99name)) == NULL) {
      warning (messages[fn_idx], "Template for function %s not found.\n",
	       c99name);
      return NULL;
    }
  }

  if ((cache_entry = fn_tmpl_is_cached (M_NAME(messages[fn_idx]))) == NULL) {
    if ((preprocessed_template = preprocess_template (template)) != NULL) {

    /*
     *  TO DO - Try doing this with parse ().
     */
    int token_type;
    long long token_start;
    MESSAGE *m;

    tmpl_size = strlen (preprocessed_template);
    tmpl_idx = 0;

    if ((m = new_message()) == NULL)
      _error ("clib_fn_expr (): Invalid new_message.\n");
    while (tmpl_idx < tmpl_size) {

      token_start = tmpl_idx;

      token_type = lexical (preprocessed_template, &tmpl_idx, m);
      if (token_type == LABEL) {
	if (is_c_function_declaration (&preprocessed_template[token_start])) {
	  template -> tmpl_fn_name = strdup (m -> name);
	  break;
	}
      }
    }
    delete_message (m);

    strcpy (template_info.fn_name, M_NAME(messages[fn_idx]));

    /*
     *  Fills in template_info parameters.  Does a lot
     *  of other things also which seem to be unnecessary
     *  now, so check if we can pare it down.
     *
     *  Once we have the params, then get the args.
     *  We do all this because we might need to replace
     *  the C args with objects.
     */
    template_params (preprocessed_template, template -> tmpl_fn_name);
    c_tmpl_fn_args (messages, fn_obj, fn_idx);
    c_template = insert_c_methd_return_type (preprocessed_template, 
				template -> tmpl_fn_name);

    strcpy (exprbuf, fmt_template_rt_expr (template));

    init_c_fn_class ();

    /*
     * "Object" is a placeholder for the actual class from
     * template_return_class ().
     *  Also don't forget add_method_return ()
     */
    generate_fn_template_init (template -> tmpl_fn_name, "Object");
    cache_fn_tmpl_name (template_info.fn_name, template -> tmpl_fn_name);
    buffer_fn_template (c_template);

    free (preprocessed_template);
    free (c_template);
    } /* if ((preprocessed_template = preprocess_template (template))... */

  } else {
    sscanf (cache_entry, TEMPLATE_CACHE_FMT, fn_name, method_name);
    template_params (template->def, template -> tmpl_fn_name);
    c_tmpl_fn_args (messages, fn_obj, fn_idx);
    strcpy (exprbuf, fmt_template_rt_expr (template));
  }

  *template_info.fn_name = 0;
  for (n = 0; n < template_info.n_params; n++) {
    free (template_info.params[n]);
    template_info.params[n] = NULL;
  }
  template_info.n_params = 0;
  for (n = 0; n < template_info.n_args; n++) {
    if ((!get_object (template_info.args[n]->__o_name, 
		      template_info.args[n]->__o_classname)) &&
	!saved_method_object (template_info.args[n])) {
      delete_object (template_info.args[n]);
    }
    template_info.args[n] = NULL;
  }
  template_info.n_args = 0;

  return exprbuf;
}

/*
 *  Generate the __ctalk_init () calls for Expr and CFunction
 *  classes if necessary.
 */
void init_c_fn_class (void) {
  OBJECT *class_obj;
  static char buf[MAXMSG];
  char addr_buf[MAXMSG];

  if (!expr_class_defined) {
    class_obj = create_object ("Class", EXPR_CLASSNAME);
    strcpy (class_obj -> __o_superclassname, EXPR_SUPERCLASSNAME);
    class_obj -> __o_superclass = get_class_object (EXPR_SUPERCLASSNAME);
    sprintf (addr_buf, "%#x", (unsigned int)class_obj);
    __ctalkSetObjectValue (class_obj, addr_buf); 
    add_class_object (class_obj);
    sprintf (buf, "__ctalk_arg (\"%s\", \"class\", \"%s\");\n", 
	     EXPR_SUPERCLASSNAME, EXPR_CLASSNAME);
    global_init_statement (buf, FALSE);
    sprintf (buf, "__ctalk_primitive_method (\"%s\", \"class\", 0);\n",
	     EXPR_SUPERCLASSNAME);
    global_init_statement (buf, FALSE);
    expr_class_defined = True;
  }

  if (!cfunction_class_defined) {
    class_obj = create_object ("Class", CFUNCTION_CLASSNAME);
    strcpy (class_obj -> __o_superclassname, CFUNCTION_SUPERCLASSNAME);
    class_obj -> __o_superclass = get_class_object(CFUNCTION_SUPERCLASSNAME);
    sprintf (addr_buf, "%#x", (unsigned int)class_obj);
    __ctalkSetObjectValue (class_obj, addr_buf);
    add_class_object (class_obj);
    sprintf (buf, "__ctalk_arg (\"%s\", \"class\", \"%s\");\n", 
	     CFUNCTION_SUPERCLASSNAME, CFUNCTION_CLASSNAME);
    global_init_statement (buf, FALSE);
    sprintf (buf, "__ctalk_primitive_method (\"%s\", \"class\", 0);\n",
	     CFUNCTION_SUPERCLASSNAME);
    global_init_statement (buf, FALSE);
    cfunction_class_defined = True;
  }
}

void generate_fn_template_init (char *template_name, char *return_class) {
  static char buf[MAXMSG];
  sprintf 
    (buf, 
     "__ctalkDefineClassMethod (\"CFunction\", \"%s\", (OBJECT *(*)())%s, %d);\n",
     template_name, template_name, template_info.n_params);
  global_init_statement (buf, FALSE);
  sprintf 
    (buf,
     "__ctalkClassMethodInitReturnClass (\"CFunction\", \"%s\", \"%s\");\n",
     template_name, (return_class) ? return_class : "Any");
  global_init_statement (buf, FALSE);
}

void init_fn_templates (void) {
  pending_templates = templates_ptr = NULL;
  tmpl_messageptr = N_MESSAGES;
}

int buffer_fn_template (char *template_buf) {

  LIST *l;

  l = new_list ();
  l -> data = (void *) strdup (template_buf);

  if (!templates_ptr) {
    pending_templates = templates_ptr = l;
  } else {
    list_add (pending_templates, l);
    templates_ptr = l;
  }

  return SUCCESS;
}

int unbuffer_fn_templates (void) {

  LIST *l;

  if (!templates_ptr) return SUCCESS;

  while (pending_templates) {
    l = list_unshift (&pending_templates);
    __fileout ((char *) l -> data);
    __fileout ("\n");
    delete_list_element (l);
    templates_ptr = pending_templates;
  }

  return SUCCESS;
}

char *template_return_class (char *template) {
  return NULL;
}

/*
 *  Replacing the template parameters is also easier before
 *  preprocessing.
 */

char *template_params (char *template, char *template_name) {

  int i, j,
    stack_begin,
    stack_end,
    param_start_ptr,
    param_end_ptr,
    method_name_ptr,
    n_params;
  char *params[MAXARGS];
  static char f_tmpl[MAXMSG];
  MESSAGE *m;
  Boolean is_param;

  stack_begin = stack_start (tmpl_messages);
  stack_end = tokenize (tmpl_message_push, template);

  for (method_name_ptr = stack_begin; 
       method_name_ptr >= stack_end; 
       method_name_ptr--)
    if (!strcmp (template_name, tmpl_messages[method_name_ptr] -> name))
      break;

  if ((param_start_ptr = scanforward (tmpl_messages, method_name_ptr,
				      stack_end, OPENPAREN)) == ERROR)
    _error ("template_params: parser error.\n");

  if ((param_end_ptr = match_paren (tmpl_messages, param_start_ptr,
				    stack_end)) == ERROR)
    _error ("template_params: parser error.\n");

  fn_param_declarations (tmpl_messages, param_start_ptr, param_end_ptr,
			       params, &n_params);
  template_info.n_params = n_params;

  for (i = n_params - 1; i >= 0; i--) {
    template_info.params[i] = method_param (params[i]);
    while(__ctalkPendingException ()) {
      /*
       *  Handle with run-time exception functions, because the
       *  error locations are from a different stack if they
       *  originated in method_param () or previous.  Note that
       *  exceptions here, due to C types and typedefs that don't
       *  map directly to classes, are no-ops at the present.
       */
      __ctalkHandleRunTimeException ();
    }
    if (! param_class_exists (template_info.params[i] -> class))
      __ctalkExceptionInternal (tmpl_messages[stack_begin],
				    undefined_class_x,
				    template_info.params[i] -> class);
    free (params[i]);
  }

  for (i = stack_begin, *f_tmpl = 0, is_param = False; i >= stack_end; i--) {
    /*
     *  Elide the C arguments.
     */
    if (i == param_start_ptr) {
      strcat (f_tmpl, METHOD_C_PARAM);
      i = param_end_ptr;
    } else {
      /*
       *  Replace params with library calls.
       */
      m = tmpl_messages[i];
      if (m -> tokentype == LABEL) {
	for (j = 0; j < template_info.n_params; j++) {
	  if (!strcmp (m -> name, template_info.params[j] -> name)) {
	    method_arg_accessor_fn (tmpl_messages, i, 
				    template_info.n_params - 1 - j);
	    strcat (f_tmpl, m -> name);
	    is_param = True;
	  }
	}
	if (!strcmp (m -> name, "self")) {
	  strcat (f_tmpl, "__ctalk_self_internal ()");
	} else {
	  if (!is_param)
	    strcat (f_tmpl, m -> name);
	  else
	    is_param = False;
	}
      } else {  /* if (m -> tokentype == LABEL) */
	strcat (f_tmpl, m -> name);
      }
    }
  }

  for (i = stack_end; i <= stack_begin; i++) {
    delete_message (tmpl_message_pop ());
  }
  return f_tmpl;
}

struct _argstr {
  char *arg;
  int start_idx,
    end_idx;
} argstrs[MAXARGS];
int argstrptr;

int c_tmpl_fn_args (MESSAGE_STACK messages, OBJECT *arg_object, 
		    int fn_ptr) {

  int arglist_start_ptr,
    arglist_end_ptr,
    arg_start_ptr,
    arg_end_ptr,
    method_ptr,
    rcvr_ptr,
    i,
    n_parens;
  char arg_buf[MAXMSG];
  METHOD *m;
  OBJECT *rcvr_class_object,
    *fn_arg_object;
  MESSAGE *m_tok;
  ERROR_LOCATION error_loc;

  if (strncmp (arg_object -> __o_name, messages[fn_ptr] -> name,
	       strlen (messages[fn_ptr] -> name)))
    _error ("c_tmpl_fn_args: Parser error.\n");

  if ((arglist_start_ptr = nextlangmsg (message_stack (), fn_ptr)) 
      == ERROR)
    _error ("c_tmpl_fn_args: Parser error.\n");

  if ((arglist_end_ptr = match_paren (messages, arglist_start_ptr,
				   get_stack_top (messages))) == ERROR)
    _error ("c_tmpl_fn_args: Parser error.\n");

  if ((messages[arglist_start_ptr] -> tokentype != OPENPAREN) ||
      (messages[arglist_end_ptr] -> tokentype != CLOSEPAREN))
    _error ("c_tmpl_fn_args: Parser error.\n");

  /*
   *  Get the information needed for an eval_arg () call.
   */

  if ((method_ptr = scanback (messages, fn_ptr, stack_start (messages),
			      METHODMSGLABEL)) != ERROR) {
    if ((rcvr_ptr = prevlangmsg (messages, method_ptr)) != ERROR) {
      if ((m = get_instance_method (messages[rcvr_ptr],
				    messages[rcvr_ptr] -> obj,
				    messages[method_ptr] -> name,
				    TRUE)) == NULL) {
	warning (messages[fn_ptr], "Unknown method.");
	return ERROR;
      }
    } else {
      char *c99name;
      /*
       *  If we have a libc function name, then there may not be
       *  a receiver.
       */
      if ((c99name = c99_name (M_NAME(messages[fn_ptr]), TRUE)) != NULL) {
	return SUCCESS;
      } else {
	warning (messages[fn_ptr], "Unknown receiver.");
	return ERROR;
      }
    }
  } else {
    warning (messages[fn_ptr], "Unknown function context.");
    return ERROR;
  }

  rcvr_class_object = 
    get_class_object (messages[rcvr_ptr] -> obj -> __o_classname);

  for (i = arglist_start_ptr, *arg_buf = 0, n_parens = 0, argstrptr = 0,
	 arg_start_ptr = -1, arg_end_ptr = -1; 
       i >= arglist_end_ptr; i--) {

    m_tok = messages[i];

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

    switch (m_tok -> tokentype)
      {
      case OPENPAREN:
	if (++n_parens > 1) {
	  if (arg_start_ptr == -1) arg_start_ptr = i;
	  strcat (arg_buf, m_tok -> name);
	}
	break;
      case CLOSEPAREN:
	switch (--n_parens)
	  {
	  case 0:
	    arg_end_ptr = prevlangmsg (messages, i);
	    if ((arg_start_ptr != -1) && 
		(M_TOK(messages[arg_end_ptr]) != CLOSEPAREN)) {
	      argstrs[argstrptr].start_idx = arg_start_ptr;
	      argstrs[argstrptr].end_idx = arg_end_ptr;
	      argstrs[argstrptr].arg = strdup (arg_buf);
	      arg_start_ptr = arg_end_ptr = -1;
	      *arg_buf = 0;
	      ++argstrptr;
	    }
	    break;
	  default:
	    strcat (arg_buf, m_tok -> name);
	    break;
	  }
	break;
      case ARGSEPARATOR:
	arg_end_ptr = prevlangmsg (messages, i);
	argstrs[argstrptr].start_idx = arg_start_ptr;
	argstrs[argstrptr].end_idx = arg_end_ptr;
	argstrs[argstrptr].arg = strdup (arg_buf);
	arg_start_ptr = arg_end_ptr = -1;
	*arg_buf = 0;
	++argstrptr;
	break;
      default:
	strcat (arg_buf, m_tok -> name);
	if (arg_start_ptr == -1)
	  arg_start_ptr = i;
      }
  }

  arg_error_loc (current_parser () -> level,
		 messages[fn_ptr] -> error_line,
		 messages[fn_ptr] -> error_column,
		 &error_loc);

  for (i = 0; i < argstrptr; i++) {
    fn_arg_object = eval_arg (m, rcvr_class_object, argstrs[i].arg, 
			      fn_ptr);
    free (argstrs[i].arg);
    template_info.args[argstrptr - 1 - i] = fn_arg_object;
    template_info.n_args++;
  }

  return SUCCESS;
}

/*
 *  The template method name is the first label in the template.
 *  This is much easier to find before preprocessing.
 */

char *find_tmpmethodname (char *template) {

  static char name[MAXLABEL];
  int i,
    stack_begin_ptr,
    stack_end_ptr;
  MESSAGE *m;

  memset ((void *)name, 0, MAXLABEL);

  stack_begin_ptr = stack_start (tmpl_messages);
  stack_end_ptr = tokenize (tmpl_message_push, template);

  for (i = stack_begin_ptr; i >= stack_end_ptr; i--) {

    m = tmpl_messages[i];

    if (M_ISSPACE (m)) continue;

    switch (m -> tokentype)
      {
      case LABEL:
	strcpy (name, m -> name);
	goto name_found;
	break;
      default:
	break;
      }
  }

 name_found:
  for (i = stack_end_ptr; i <= stack_begin_ptr; i++) 
    delete_message (tmpl_message_pop ());

  return name;
}

int add_method_return (char *template, char *buf) {

  int i,
    stack_begin_ptr,
    stack_end_ptr;
  Boolean have_name;
  MESSAGE *m;

  stack_begin_ptr = stack_start (tmpl_messages);
  stack_end_ptr = tokenize (tmpl_message_push, template);

  for (i = stack_begin_ptr, have_name = False; i >= stack_end_ptr; i--) {

    m = tmpl_messages[i];

    switch (m -> tokentype)
      {
      case LABEL:
	if (have_name == False) {
	  strcat (buf, METHOD_RETURN);
	  have_name = True;
	}
	break;
      default:
	break;
      }
    strcat (buf, m -> name);
  }

  for (i = stack_end_ptr; i <= stack_begin_ptr; i++) 
    delete_message (tmpl_message_pop ());

  return SUCCESS;
}

/*
 *  Call ctpp to preprocess the template file.  Do not include
 *  line numbers in ctpp's output.  
 *  
 *  NOTE: In the case of include 
 *  files in the template that are also included in the input
 *  file, there should not be an issue with redefining symbols,
 *  like typedefs.
 */

char *preprocess_template (FN_TMPL *template) {

  char cmd[MAXMSG];
  char ctpp_path[FILENAME_MAX];
  char ctpp_ofn[FILENAME_MAX];
  char argbuf[MAXMSG],
    cachebuf[MAXMSG],
    oldcache[FILENAME_MAX];
  static char *outbuf;
  int retval;
  FILE *P;

  strcpy (ctpp_path, which (CTPP_BIN));
  strcpy (ctpp_ofn, ctpp_name (template -> name, TRUE));

  create_tmp ();
  write_tmp (template -> def);
  close_tmp ();

  if (macro_cache_path ()) {
    strcpy (oldcache, macro_cache_path ());
    if (file_exists (oldcache)) {
      sprintf (cachebuf, "-imacros %s ", macro_cache_path ());
      strcpy (argbuf, cachebuf);
    }
  } else {
    *argbuf = 0;
    *oldcache = 0;
  }

  sprintf (cachebuf, "-dF %s ", new_macro_cache_path ());
  strcat (argbuf, cachebuf);

  sprintf (cmd, "%s -P %s %s %s", ctpp_path, argbuf, get_tmpname (), ctpp_ofn);

  if ((P = popen (cmd, "w")) == NULL) {
    printf ("%s: %s.\n", ctpp_ofn, strerror (errno));
    return NULL;
  }

  if ((retval = pclose (P)) == ERROR) {
    cleanup (TRUE);
    exit (retval);
  }

  if (*oldcache && file_exists (oldcache)) unlink (oldcache);

  unlink_tmp ();

  if (!file_exists (ctpp_ofn))
    return NULL;

  p_read_file (&outbuf, ctpp_ofn);

  unlink (ctpp_ofn);

  return outbuf;

}

static FN_TMPL *template_cache = NULL;
static FN_TMPL *template_cache_ptr;

/*
 *  This might be able to replace init_fn_templates (), and
 *  related functions.
 */

void init_fn_template_cache (void) {
  template_cache = template_cache_ptr = NULL;
  tmpl_messageptr = N_MESSAGES;
}

FN_TMPL *new_fn_template (void) {

  FN_TMPL *f;

  if ((f = calloc (1, sizeof (struct _fn_tmpl))) == NULL)
    _error ("new_template (): %s.\n", strerror (errno));

  strcpy (f -> sig, "FNTMPL");

  return f;

}

FN_TMPL *get_template (char *c99name) {

  FN_TMPL *f;

  for (f = template_cache; f; f = f -> next) 
    if (!strcmp (c99name, f -> name))
      return f;

  return NULL;
}

/*
 *  After preprocessing, the output looks like set of 
 *  #define statement lines, and this function parses
 *  them into FN_TMPL structures and caches them.
 */

FN_TMPL *cache_template (char *c99name) {

  char *filebuf;
  char *linebuf;
  char namebuf[MAXLABEL+1], methodnamebuf[MAXLABEL];
  char *d_ptr, *n_ptr,
    *eos_ptr,
    *s1_ptr, *s2_ptr, *s3_ptr,
    *nl_ptr;
  int r;
  FN_TMPL *fn_tmpl,
    *this_tmpl;

  /*
   *  Fills in ctpp_ofn_template.
   */
  if (preprocess_template_file (c99name))
    return NULL;

  this_tmpl = NULL;

  if ((linebuf = calloc (file_size (ctpp_ofn_template) + 1, sizeof (char))) == NULL)
    _error ("cache_template (): %s.\n", strerror (errno));

  if ((r = p_read_file (&filebuf, ctpp_ofn_template)) == 0)
    _error ("cache_template (): %s.\n", strerror (errno));


  d_ptr = n_ptr = filebuf;
  eos_ptr = &filebuf[strlen(filebuf)];
  while (1) {

    if ((n_ptr = index (d_ptr, '\n')) != NULL) {
      memset ((void *)linebuf, 0, file_size (ctpp_ofn_template) + 1);
      strncpy (linebuf, d_ptr, n_ptr - d_ptr);

      /*
       *  Line splices in the template definition are replaced
       *  with a space character during preprocessing.
       */
      while ((nl_ptr = strstr (linebuf, "\\n ")) != NULL) {
	*nl_ptr++ = '\n'; 
	if (*nl_ptr) *nl_ptr++ = ' ';
	if (*nl_ptr) *nl_ptr++ = ' ';
      }

      fn_tmpl = new_fn_template ();
      s1_ptr = index (linebuf, ' ');
      s2_ptr = index (s1_ptr + 1, ' ');

      memset ((void *)namebuf, 0, MAXLABEL);
      substrcpy (namebuf, s1_ptr + 1, 0, s2_ptr - (s1_ptr + 1));

      for (; isspace((int)*s2_ptr); ++s2_ptr)
	;
      s3_ptr = index (s2_ptr, ' ');
      substrcpy (methodnamebuf, s2_ptr, 0, s3_ptr - s2_ptr);

      fn_tmpl -> name = strdup (namebuf);
      fn_tmpl -> tmpl_fn_name = strdup (methodnamebuf);
      fn_tmpl -> def = strdup (s2_ptr);

      if (!strcmp (c99name, fn_tmpl -> name))
	this_tmpl = fn_tmpl;

      if (!template_cache_ptr) {
	template_cache = template_cache_ptr = fn_tmpl;
      } else {
	template_cache_ptr -> next = fn_tmpl;
	fn_tmpl -> prev = template_cache_ptr;
	template_cache_ptr = fn_tmpl;
      }
    }

    if ((d_ptr = index (n_ptr, '#')) == NULL)
      break;
  }


  free (linebuf);
  free (filebuf);

  unlink (ctpp_ofn_template);

  return this_tmpl;
}

/*
 *  Use the preprocessor to split the template file into #define
 *  statements.  There should not be any need for predefined 
 *  macros or include files, if the templates are self-contained.
 */

int preprocess_template_file (char *c99name) {

  char *template_fn;
  char cmd[MAXMSG];
  int retval;
  FILE *P;

  if ((template_fn = get_clib_template_fn (c99name)) == NULL)
    return ERROR;

  strcpy (ctpp_ofn_template, ctpp_name (template_fn, TRUE));
  sprintf (cmd, "%s -undef -dM %s > %s", 
	   which (CTPP_BIN), template_fn, ctpp_ofn_template);


  if ((P = popen (cmd, "w")) == NULL) {
    printf ("%s: %s.\n", template_fn, strerror (errno));
    return ERROR;
  }

  if ((retval = pclose (P)) == ERROR) {
    cleanup (TRUE);
    exit (retval);
  }

  /*
   *  If the preprocessor encounters a fatal error, it
   *  will not produce an output file.
   */
  if (file_exists (ctpp_ofn_template))
    return SUCCESS;
  else
    return ERROR;
}

/*
 *  Turn the template into a function declaration.
 *  The only thing it does now is insert the 
 *  OBJECT * method return type into the template.
 *
 *  TO DO - There should be a little more analysis
 *  of the template declaration, in case someone
 *  includes the return type in the template.
 */

char *insert_c_methd_return_type (char *buf, char *fn_name) {

  char *fn_name_ptr, *newbuf;
  int defsize;

  if ((fn_name_ptr = strstr (buf, fn_name)) == NULL) 
    return NULL;
  
  defsize = strlen (buf);

  if ((newbuf = calloc (defsize + strlen (METHOD_RETURN) + 1, sizeof (char)))
      == NULL)
    _error ("c_tmpl_fn_decl (): %s.\n", strerror (errno));
  
  strncpy (newbuf, buf, fn_name_ptr - buf);
  strcat (newbuf, METHOD_RETURN);
  strcat (newbuf, fn_name_ptr);

  return newbuf;
  
}

void cache_fn_tmpl_name (char *fn_name, char *method_name) {
  LIST *l;
  char buf[MAXMSG];
  l = new_list ();
  sprintf (buf, TEMPLATE_CACHE_FMT, fn_name, method_name);
  l -> data = (void *) strdup (buf);
  if (!cached_fn_ptr) {
    cached_fns = cached_fn_ptr = l;
  } else {
    list_add (cached_fns, l);
    cached_fn_ptr = l;
  }
}

char *fn_tmpl_is_cached (char *fn_name) {

  LIST *l;

  for (l = cached_fns; l ; l = l -> next) {
    if (!strncmp (fn_name, (char *)l -> data, strlen (fn_name)))
      return (char *)l -> data;
  }

  return NULL;
}

char *fmt_template_rt_expr (FN_TMPL *template) {

  static char exprbuf[MAXMSG];
  char tmpargbuf[MAXMSG], argbuf[MAXMSG];
  int i;

  if (template_info.n_params) {
    strcpy (argbuf, "(");

    for (i = 0; i < template_info.n_args; i++) {
      sprintf (tmpargbuf, "%s", 
	       template_info.args[template_info.n_args - i - 1]->__o_name);
      if (i != (template_info.n_args - 1)) {
	strcat (tmpargbuf, ", ");
      }
      strcat (argbuf, tmpargbuf);
    }
    strcat (argbuf, ")");
    sprintf (exprbuf, "%s %s %s", CFUNCTION_CLASSNAME, 
	     template -> tmpl_fn_name,
	     argbuf);
  } else {/* if (template_info.n_params) { */
    sprintf (exprbuf, "%s %s", CFUNCTION_CLASSNAME, 
	     template -> tmpl_fn_name);
  }
  return exprbuf;
}

char *fn_param_prototypes_from_cfunc (CFUNC *fn, char *buf) {

  CVAR *_param = NULL;

  if (fn -> params == NULL) {
    strcpy (buf, "void");
  } else {
    for (_param = fn -> params, *buf = 0; _param; _param = _param -> next) {
      declaration_expr_from_cvar (_param, buf);
      if (_param -> next) {
	strcat (buf, ", ");
      }
    }
  }
  return buf;
}

int find_fn_arg_parens (MESSAGE_STACK messages, int fn_idx, 
			int *arg_paren_start_idx, 
			int *arg_paren_end_idx) {

  if ((*arg_paren_start_idx = nextlangmsg (messages, fn_idx)) == ERROR)
    error (messages[fn_idx], "find_fn_arg_parens: Parser error.");

  if ((*arg_paren_end_idx = match_paren (messages, *arg_paren_start_idx, 
					get_stack_top (messages))) == ERROR)
    error (messages[fn_idx], "find_fn_arg_parens: Parser error.");

  return SUCCESS;
}

void cleanup_template_cache (void) {
  FN_TMPL *prev;
  if (!template_cache)
    return;
  while (template_cache_ptr != template_cache) {
    prev = template_cache_ptr -> prev;
    free (template_cache_ptr -> name);
    free (template_cache_ptr -> tmpl_fn_name);
    free (template_cache_ptr -> def);
    template_cache_ptr = prev;
  }
  free (template_cache -> name);
  free (template_cache -> tmpl_fn_name);
  free (template_cache -> def);
}
