/* $Id: rt_expr.c,v 1.22 2009/03/01 03:50:23 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 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 "object.h"
#include "message.h"
#include "ctalk.h"
#include "objtoc.h"

/*
 *  Simple or complex expression that should be evaled at run time.
 */
char *fmt_rt_expr (MESSAGE_STACK messages, int start_idx, int *end_idx) {
  static char buf[MAXMSG];
  char *exprbuf, exprbuf_esc[MAXMSG];

  exprbuf = collect_expression (messages, start_idx, end_idx, 
				stack_start (messages),
				get_stack_top (messages));
  strcpy (exprbuf_esc, escape_str_quotes (exprbuf));
  free (exprbuf);
  sprintf (buf, "%s(\"%s\")", EVAL_EXPR_FN, exprbuf_esc);
  return buf;
}

char *rt_expr (MESSAGE_STACK messages, int start_idx, int *end_idx) {

  char *exprbuf;
  int i, struct_expr_limit;

  exprbuf = fmt_rt_expr (messages, start_idx, end_idx);
  for (i = start_idx; i >= *end_idx; i--) {
    if (M_TOK(messages[i]) == LABEL) {
      CVAR *__c;
      /* Instance or class variable message */
      if (messages[i]->receiver_obj != NULL) {
      } else {
	if (((__c = get_local_var (M_NAME(messages[i]))) != NULL) ||
	    ((__c = get_global_var (M_NAME(messages[i]))) != NULL)) {
	  if (is_struct_or_union_expr (messages, i, 
				       stack_start(messages),
				       get_stack_top (messages))) {
	    generate_register_c_method_arg_call 
	      (__c, 
	       struct_or_union_expr (messages, i, 
				     get_stack_top(messages),
				     &struct_expr_limit),
	       LOCAL_VAR,
	       start_idx);
	    i = struct_expr_limit;
	  } else {
	    generate_register_c_method_arg_call (__c, M_NAME(messages[i]),
						 LOCAL_VAR,
						 start_idx);
	  }
	}
      }
    }
  }
  fileout (exprbuf, 0, start_idx);
  for (i = start_idx; i >= *end_idx; i--) {
    ++messages[i] -> evaled;
    ++messages[i] -> output;
  }
  return exprbuf;
}

static char *fn_param_return_trans (MESSAGE *m_orig, CFUNC *cfn, char *expr, int arg_idx) {
  static char arg_expr_buf[MAXMSG];
  char arg_classname[MAXLABEL];
  int __n_th_arg;
  CVAR *__arg;
  if (cfn -> params && IS_CVAR(cfn -> params)) {
    for (__n_th_arg = 0, __arg = cfn -> params; 
	 __n_th_arg < arg_idx; __n_th_arg++) 
      __arg = __arg -> next;
    strcpy (arg_classname, basic_class_from_cvar (__arg, 0));
    strcpy (arg_expr_buf, fmt_rt_return (expr, arg_classname, TRUE));
  } else {
    warning 
      (m_orig, 
       "Function, \"%s,\" has no paramter definitions.",
       cfn -> decl);
    warning 
      (m_orig,
       "Parameter class defaulting to, \"Integer.\"\n");
    strcpy 
      (arg_expr_buf, 
       fmt_rt_return (expr, INTEGER_CLASSNAME, TRUE));
  }
  return arg_expr_buf;
}

/*
 *  This should always be in c_argument_context.
 *  TODO - Also call from rt_self_expr, below.
 */
char *rt_obj_arg (MESSAGE_STACK messages, 
		   int arg_start_idx, int *arg_end_idx) {
  int i, stack_end, lookahead, n_parens, arg_idx, frame_start_idx,
    fn_idx, struct_expr_limit;
  MESSAGE *m;
  char *expr_ptr, arg_buf[MAXMSG], expr_buf[MAXMSG];
  static char arg_expr_buf[MAXMSG];
  stack_end = get_stack_top (messages);
  for (i = arg_start_idx, n_parens = 0; i > stack_end; i--) {
    m = messages[i];
    lookahead = nextlangmsg (messages, i);
    switch (M_TOK(m))
      {
      case OPENPAREN:
	++n_parens;
	break;
      case CLOSEPAREN:
	--n_parens;
      default:
	if ((M_TOK(messages[lookahead]) == CLOSEPAREN) ||
	    (M_TOK(messages[lookahead]) == ARGSEPARATOR)) {
	  if (!n_parens) {
	    *arg_end_idx = i;
	    goto got_arg_limit;
	  }
	}
	break;
      }
  }
 got_arg_limit:
  expr_ptr = collect_tokens (messages, arg_start_idx, *arg_end_idx);
  strcpy (arg_buf, expr_ptr);
  free (expr_ptr);
  sprintf (expr_buf, "%s (\"%s\")", EVAL_EXPR_FN, arg_buf);
  /*
   *  NOTE - In a very complex expression, this might need to be the 
   *  frame start of the function or control block, if different 
   *  than the frame of the argument.
   */
  frame_start_idx = 
    frame_at (current_parser () -> frame) -> message_frame_top;

  for (i = arg_start_idx; i >= *arg_end_idx; i--) {
    if (M_TOK(messages[i]) == LABEL) {
      CVAR *__c;
      if (((__c = get_local_var (M_NAME(messages[i]))) != NULL) ||
 	  ((__c = get_global_var (M_NAME(messages[i]))) != NULL)) {
	if (is_struct_or_union_expr (messages, i, 
				     stack_start(messages),
				     get_stack_top (messages))) {
	  generate_register_c_method_arg_call 
	    (__c, 
	     struct_or_union_expr (messages, i, 
				   get_stack_top(messages),
				   &struct_expr_limit),
	     LOCAL_VAR,
	     frame_start_idx);
	  i = struct_expr_limit;
	} else {
	  generate_register_c_method_arg_call (__c, M_NAME(messages[i]),
					       LOCAL_VAR,
					       frame_start_idx);
	}
      }
    }
  }

  if ((arg_idx =
         obj_expr_is_arg (messages, arg_start_idx,
  			stack_start (messages),
  			get_stack_top (messages),
  			&fn_idx)) != ERROR) {
    CFUNC *cfn;
    if ((cfn = get_function (M_NAME(messages[fn_idx]))) != NULL) {
      strcpy (arg_expr_buf, 
	      fn_param_return_trans (messages[fn_idx], 
				     cfn, expr_buf, arg_idx));
    }
  } else {
    _warning ("Can't find self argument context in rt_self_arg.\n");
    strcpy (arg_expr_buf, expr_buf);
  }

  fileout (arg_expr_buf, 0, arg_start_idx);
  for (i = arg_start_idx; i >= *arg_end_idx; i--) {
    ++messages[i] -> evaled;
    ++messages[i] -> output;
  }
  return arg_expr_buf;
}

char *rt_self_expr (MESSAGE_STACK messages, int start_idx, int *end_idx) {

  char *exprbuf, arg_classname[MAXLABEL];
  static char arg_expr_buf[MAXMSG];
  int i;
  int fn_idx, arg_idx;
  int lookback;
  int struct_expr_limit;
  OBJECT_CONTEXT context;
  CVAR *c, *struct_defn, *struct_decl;

  /*
   *  Do not free ().
   */
  exprbuf = fmt_rt_expr (messages, start_idx, end_idx);
  for (i = start_idx; i >= *end_idx; i--) {
    if (M_TOK(messages[i]) == LABEL) {
      CVAR *__c;
      if (((__c = get_local_var (M_NAME(messages[i]))) != NULL) ||
 	  ((__c = get_global_var (M_NAME(messages[i]))) != NULL)) {
	if (is_struct_or_union_expr (messages, i, 
				     stack_start(messages),
				     get_stack_top (messages))) {
	  generate_register_c_method_arg_call 
	    (__c, 
	     struct_or_union_expr (messages, i, 
				   get_stack_top(messages),
				   &struct_expr_limit),
	     LOCAL_VAR,
	     start_idx);
	  i = struct_expr_limit;
	} else {
	  generate_register_c_method_arg_call (__c, M_NAME(messages[i]),
					       LOCAL_VAR,
					       start_idx);
	}
      }
    }
  }
  if ((arg_idx = 
       obj_expr_is_arg (messages, start_idx, 
			stack_start (messages),
			get_stack_top (messages),
			&fn_idx)) != ERROR) {
    CFUNC *cfn;
    if ((cfn = get_function (M_NAME(messages[fn_idx]))) != NULL) {
      strcpy (arg_expr_buf, 
	      fn_param_return_trans (messages[fn_idx], 
				     cfn, exprbuf, arg_idx));
    }
  } else {
    context = object_context (messages, start_idx);
    switch (context)
      {
      case c_context:
      case c_argument_context:
	/*
	 *  This is the same procedure that object_context
	 *  uses to look for a C argument context.
	 */
	lookback = start_idx + 1;
	while ((lookback = prevlangmsg (messages, lookback)) != ERROR) {
	  if ((messages[lookback] -> tokentype == LABEL) &&
	      (messages[lookback] -> obj == NULL))
	    break;
	}
	if (lookback == ERROR) {
	  strcpy (arg_expr_buf, exprbuf);
	  goto rt_expr_evaled;
	}
	if (((c = get_local_var (M_NAME(messages[lookback]))) != NULL) ||
	    ((c = get_global_var (M_NAME(messages[lookback]))) != NULL)) {
	  strcpy (arg_classname, basic_class_from_cvar (c, 0));
	  strcpy (arg_expr_buf, fmt_rt_return (exprbuf, arg_classname, TRUE));
	} else {
	  if (is_struct_member (M_NAME(messages[lookback]))) {
	    int lookback2;
	    int last_expr_tok;
	    char *agg_expr;
	    last_expr_tok = lookback;
	    while ((lookback = prevlangmsg (messages, lookback)) != ERROR) {
	      if (((lookback2 = prevlangmsg (messages, lookback)) != ERROR) 
		  && ((M_TOK(messages[lookback2]) != PERIOD) ||
		      (M_TOK(messages[lookback2]) != DEREF)))
		break;
	    }
	    if (((struct_decl = 
		 get_local_var (M_NAME(messages[lookback2]))) == NULL) &&
		((struct_decl =
		  get_global_var (M_NAME(messages[lookback2]))) == NULL)) {
	      warning (messages[start_idx], 
		       "Definition of struct or union, \"%s,\" not found.",
		       M_NAME(messages[lookback2]));
	      strcpy (arg_expr_buf, exprbuf);
	    } else {
	      if (struct_decl -> attrs == CVAR_ATTR_STRUCT) {
		if (((struct_defn = get_global_var (struct_decl->type))
		    == NULL) &&
		    ((struct_defn = get_local_var (struct_decl->type))
		     == NULL)) {
		  warning (messages[start_idx], 
		   "Definition of struct or union, \"%s,\" not found.",
		   struct_decl -> type);
		  struct_defn = struct_decl;
		}
	      }
	      agg_expr = collect_tokens (messages, lookback2, last_expr_tok);
	      c = struct_member_from_expr (agg_expr, struct_defn);
	      strcpy (arg_classname, basic_class_from_cvar (c, 0));
	      strcpy (arg_expr_buf, 
		      fmt_rt_return (exprbuf, arg_classname, TRUE));
	      free (agg_expr);
	    }
	  } else {
	    int __s, __s2;
	    __s = stack_start (messages);
	    __s2 = get_stack_top (messages);
	    if (is_fmt_arg (messages, start_idx, __s, __s2)) {
	      strcpy 
		(arg_expr_buf, fmt_printf_fmt_arg 
		 (messages, start_idx, __s, __s2, exprbuf));
	    } else {
	      strcpy (arg_expr_buf, exprbuf);
	    }
	  }
	}
	break;
      default:
	strcpy (arg_expr_buf, exprbuf);
	break;
      }      
  }
 rt_expr_evaled:
  fileout (arg_expr_buf, 0, start_idx);
  for (i = start_idx; i >= *end_idx; i--) {
    ++messages[i] -> evaled;
    ++messages[i] -> output;
  }
  return arg_expr_buf;
}

static void comma_op_buf (char *s) {
  char *p;
  while ((p = index (s, ';')) != NULL) *p = ',';
  while ((p = index (s, '\n')) != NULL) *p = ' ';
}

/*
 *  Format a run-time expression, for example, a for loop termination
 *  predicate, with CVAR registration function calls if necessary.
 */
char *fmt_default_ctrlblk_expr (MESSAGE_STACK messages, int start_idx, 
				int end_idx, int keep) {

  static char buf[MAXMSG];
  char *exprbuf, *c99name,
    fn_call_buf[MAXMSG],
    *cfn_buf;
  int i;
  MESSAGE *m;
  CVAR *cvar;
  CFUNC *fn;

  for (i = start_idx, *buf = 0; i >= end_idx; i--) {

    m = messages[i];

    if (m && M_ISSPACE(m)) continue;

    if (M_TOK(m) == LABEL) {
      if (((cvar = get_local_var (M_NAME(m))) != NULL) ||
	  ((cvar = get_global_var (M_NAME(m))) != NULL)) {
	if ((cvar -> attrs & CVAR_ATTR_FN_DECL) ||
	    (cvar -> attrs & CVAR_ATTR_FN_PTR_DECL) ||
	    (cvar -> attrs & CVAR_ATTR_FN_PROTOTYPE)) {
	  /*
	   *  First check if we have a template for the function.
	   *  If so, insert the template in the output, and if 
	   *  necessary, register the template in the initialization
	   *  (though now the function registration by clib_fn_expr ()
	   *  is handled earlier in ctrlblk_pred_rt_expr ()).
	   *  If there's no template, issue a warning.
	   *
	   *  The function should be able to simply do a token 
	   *  replacement here, but later, it might be necessary
	   *  to set the value object, if we need to rescan the
	   *  message.
	   */
	  if ((c99name = c99_name (M_NAME(m), FALSE)) != NULL) {
	    if ((cfn_buf = clib_fn_rt_expr (messages, i)) != NULL) {
	      free (m -> name);
	      m -> name = strdup (cfn_buf);
	    }
	  }
	} else {
	  sprintf (fn_call_buf, "%s ", 
	   fmt_register_c_method_arg_call (cvar, cvar -> name, LOCAL_VAR));
	  comma_op_buf (fn_call_buf);
	  strcat (buf, fn_call_buf);
	}
      } else {
	if ((fn = get_function (M_NAME(m))) != NULL) {
	  if ((c99name = c99_name (M_NAME(m), FALSE)) != NULL) {
	    if ((cfn_buf = clib_fn_rt_expr (messages, i)) != NULL) {
	      free (m -> name);
	      m -> name = strdup (cfn_buf);
	    }
	  }
	}
      }
    }
  }

  exprbuf = collect_tokens (messages, start_idx, end_idx);
  de_newline_buf (exprbuf);
  sprintf (fn_call_buf, "%s (%s, %d)", INT_TRANS_FN, 
 	   fmt_eval_expr (exprbuf), keep);
  free (exprbuf);
  strcat (buf, fn_call_buf);

  return buf;
}

/*
 *  This is much simpler than fmt_default_ctrlblk_expr, because user functions
 *  get a template for each occurrence (in fn_tmpl.c), so we don't 
 *  have to worry about registering arguments.  
 */
char *fmt_user_fn_rt_expr (MESSAGE_STACK messages, 
			   int start_idx, int end_idx, char *expr) {
  static char fn_call_buf[MAXMSG];
  char buf[MAXMSG];
  strcpy (buf, expr);
  de_newline_buf (buf);
  sprintf (fn_call_buf, "__ctalkToCInteger (%s, %d)", 
	   fmt_eval_expr (buf), FALSE);
  return fn_call_buf;
}


