/* $Id: objtoc.c,v 1.4 2008/11/06 18:50:05 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.
*/

/* 
 *  Generate calls that translate objects to their C equivalents.  In
 *  the case of object that do not correspond directly to C data types,
 *  return the value pointer, or at least try to do something 
 *  intelligent.
 *
 *  In this version, C int and char types are synonymous, Float class
 *  translates to double, as in C, and a long double class isn't 
 *  implemented.
 *
 *  Long int and int are synonymous, as in GNU C for most, if not all, 
 *  platforms.
 *
 *  NOTE - Future versions might allow the class library to define
 *  what instance variable to return in a C context.
 */

#include <stdio.h>
#include <string.h>
#include "object.h"
#include "message.h"
#include "ctalk.h"
#include "objtoc.h"

static char *__warn_unknown_C_type_msg (char *type_name) {
  static char buf[MAXMSG];
  sprintf (buf, 
	   _("Unimplemented C type %s in conversion, class defaulting to Integer.\n"), 
	   type_name);
  return buf;
}

/*
 *  The function needs to decide whether to use the return class of
 *  the receiver object or of the method if they are different.  Use
 *  the return class of the method, unless the method is defined in a
 *  superclass and does not have a corresponding basic C data type.
 *  In that case, use the return class of the receiver object.
 */

char *obj_2_c_wrapper (MESSAGE *orig, OBJECT *object, 
		       METHOD *method, char *rt_expr, int keep) {

  char return_class_name[MAXLABEL];

  if (!method) {
    /*
     *  Look for an instance variable message and use the
     *  instance variable's class as the return class,
     *  or the object's class if necessary.
     */
    OBJECT *instancevar;
    if ((instancevar = __ctalkGetInstanceVariable (object, M_NAME(orig),
						   TRUE)) == NULL) {
      strcpy (return_class_name, object -> __o_classname);
    } else {
      if (!strcmp (instancevar -> __o_classname, "Character") ||
	  !strcmp (instancevar -> __o_classname, "Float") ||
	  !strcmp (instancevar -> __o_classname, "Integer") ||
	  !strcmp (instancevar -> __o_classname, "LongInteger") ||
	  !strcmp (instancevar -> __o_classname, "String"))
	strcpy (return_class_name, instancevar -> __o_classname);
      else
	strcpy (return_class_name, object -> __o_classname);
    }
	 
  } else {
    /*
     *  Use the method return class as the expression
     *  return class.
     */
    if (!strcmp (method -> returnclass, "Character") ||
	!strcmp (method -> returnclass, "Float") ||
	!strcmp (method -> returnclass, "Integer") ||
	!strcmp (method -> returnclass, "LongInteger") ||
	!strcmp (method -> returnclass, "String")) {
      strcpy (return_class_name, method -> returnclass);
    } else {
      if (orig -> attrs & OBJ_IS_CLASS_VAR) {
	strcpy (return_class_name,
		((object->instancevars) ? 
		object->instancevars->__o_classname :
		 object -> __o_classname));
      } else {
	strcpy (return_class_name, object -> __o_classname);
      }
    }
  }

  return fmt_rt_return (rt_expr, return_class_name, keep);
}

char *obj_2_c_wrapper_trans (MESSAGE_STACK messages, int idx, 
			     MESSAGE *orig, OBJECT *object, 
			     METHOD *method, char *rt_expr, int keep) {

  char return_class_name[MAXLABEL];

  if (is_char_ptr_fmt_arg (messages, idx, 
			   stack_start (messages),
			   get_stack_top (messages))) {
    strcpy (return_class_name, STRING_CLASSNAME);
    return fmt_rt_return (rt_expr, return_class_name, keep);
  }

  if (!method) {
    /*
     *  Look for an instance variable message and use the
     *  instance variable's class as the return class,
     *  or the object's class if necessary.
     */
    OBJECT *instancevar;
    if ((instancevar = __ctalkGetInstanceVariable (object, M_NAME(orig),
						   TRUE)) == NULL) {
      strcpy (return_class_name, object -> __o_classname);
    } else {
      if (!strcmp (instancevar -> __o_classname, "Character") ||
	  !strcmp (instancevar -> __o_classname, "Float") ||
	  !strcmp (instancevar -> __o_classname, "Integer") ||
	  !strcmp (instancevar -> __o_classname, "LongInteger") ||
	  !strcmp (instancevar -> __o_classname, "String"))
	strcpy (return_class_name, instancevar -> __o_classname);
      else
	strcpy (return_class_name, object -> __o_classname);
    }
	 
  } else {
    /*
     *  Use the method return class as the expression
     *  return class.
     */
    if (!strcmp (method -> returnclass, "Character") ||
	!strcmp (method -> returnclass, "Float") ||
	!strcmp (method -> returnclass, "Integer") ||
	!strcmp (method -> returnclass, "LongInteger") ||
	!strcmp (method -> returnclass, "String")) {
      strcpy (return_class_name, method -> returnclass);
    } else {
      if (orig -> attrs & OBJ_IS_CLASS_VAR) {
	strcpy (return_class_name,
		((object->instancevars) ? 
		object->instancevars->__o_classname :
		 object -> __o_classname));
      } else {
	strcpy (return_class_name, object -> __o_classname);
      }
    }
  }

  return fmt_rt_return (rt_expr, return_class_name, keep);
}

/*
 *  This function is, for now, only called for method
 *  expressions that have arguments.
 *  If the return class of the method is, "Any," then
 *  we should be able to use the class of the method
 *  argument(s).
 */
char *array_obj_2_c_wrapper (MESSAGE_STACK messages, int idx, OBJECT *object, 
		       METHOD *method, char *rt_expr) {

  char return_class_name[MAXLABEL];
  OBJECT *instancevar;
  static char buf[MAXMSG];
  COLLECTION_CONTEXT context;
  int rcvr_idx, arglist_start_idx, arglist_end_idx, expr_start_idx, stack_top_idx;
  int expr_paren_level, expr_open_paren_idx, expr_close_paren_idx;

  if (!strcmp (method -> returnclass, "Character") ||
      !strcmp (method -> returnclass, "Float") ||
      !strcmp (method -> returnclass, "Integer") ||
      !strcmp (method -> returnclass, "LongInteger") ||
      !strcmp (method -> returnclass, "String")) {
    strcpy (return_class_name, method -> returnclass);
    return fmt_rt_return (rt_expr, return_class_name, TRUE);
  } else {
    if (!strcmp (method -> returnclass, "Any")) {
      if ((instancevar = method -> args[0]) != NULL) {
	strcpy (return_class_name, instancevar -> __o_classname);
      } else {
	strcpy (return_class_name, object -> __o_classname);
      }
    }
  }
  
  stack_top_idx = stack_start (messages);
  for (rcvr_idx = idx + 1; rcvr_idx <= stack_top_idx; rcvr_idx++) {
    if (!strcmp (M_NAME(messages[rcvr_idx]), object->__o_name))
      break;
  }
  arglist_start_idx = nextlangmsg (messages, idx);
  arglist_end_idx = 
    method_arglist_limit (messages, arglist_start_idx, method -> n_args,
			  method -> varargs);
  /*
   *  Check for opening parentheses on the expression.
   */
  expr_paren_level = expr_outer_parens (messages, rcvr_idx, arglist_end_idx, 
                     stack_start (messages), get_stack_top (messages),
		     &expr_open_paren_idx, &expr_close_paren_idx);

  expr_start_idx = rcvr_idx;
  if (expr_paren_level) {
    int __t;
    __t = expr_start_idx;
    while ((__t = prevlangmsg (messages, __t)) != ERROR) {
      if (M_TOK(messages[__t]) == OPENPAREN) {
	expr_start_idx = __t;
	continue;
      }
      break;
    }
  }
  context = collection_context (messages, expr_start_idx);

  switch (context) 
    {
    case int_collection_context:
      sprintf (buf, "%s (%s)", ARRAY_INT_TRANS_FN, rt_expr);
      break;
    case char_ptr_collection_context:
      sprintf (buf, "%s (%s)", STRING_TRANS_FN, rt_expr);
      break;
    default:
      sprintf (buf, "%s (%s)", ARRAY_TRANS_FN, rt_expr);
      break;
    }
  return buf;
}

char *fmt_rt_return (char *exprbuf, char *returnclassname, int keep) {
  static char buf[MAXMSG];
  if (!strcmp (returnclassname, CHARACTER_CLASSNAME) ||
      is_subclass_of (returnclassname, CHARACTER_CLASSNAME)) {
    if (!strcmp (returnclassname, STRING_CLASSNAME) ||
	is_subclass_of (returnclassname, STRING_CLASSNAME)) {
      sprintf (buf, "%s (%s)", STRING_TRANS_FN, exprbuf);
      return buf;
    } else {
      sprintf (buf, "%s (%s)", CHAR_TRANS_FN, exprbuf);
      return buf;
    }
  }

  if (!strcmp (returnclassname, FLOAT_CLASSNAME) ||
      is_subclass_of (returnclassname, FLOAT_CLASSNAME)) {
    sprintf (buf, "%s (%s)", FLOAT_TRANS_FN, exprbuf);
    return buf;
  }

  if (!strcmp (returnclassname, INTEGER_CLASSNAME) ||
      is_subclass_of (returnclassname, INTEGER_CLASSNAME)) {
    sprintf (buf, "%s (%s, %d)", INT_TRANS_FN, exprbuf, keep);
    return buf;
  }

  if (!strcmp (returnclassname, LONGINTEGER_CLASSNAME) ||
      is_subclass_of (returnclassname, LONGINTEGER_CLASSNAME)) {
    sprintf (buf, "%s (%s)", LONGINT_TRANS_FN, exprbuf);
    return buf;
  }

  /*
   *  Otherwise, just return the value pointer.
   */

  sprintf (buf, "%s (%s)", PTR_TRANS_FN, exprbuf);
  return buf;
}

static struct __C_type_conv {
  char c_type_name[MAXLABEL];
  int n_derefs;
  char class_name[MAXLABEL];
} __C_type_conv [] = {
  {"char", 0, CHARACTER_CLASSNAME},
  {"char", 1, STRING_CLASSNAME},
  {"char", 2, ARRAY_CLASSNAME},
  {"int", 0, INTEGER_CLASSNAME},
  {"int", 1, SYMBOL_CLASSNAME},
  {"int", 2, ARRAY_CLASSNAME},
  {"long long int", 0, LONGINTEGER_CLASSNAME},
  {"long long int", 1, SYMBOL_CLASSNAME},
  {"long long int", 2, ARRAY_CLASSNAME},
  {"double", 0, FLOAT_CLASSNAME},
  {"double", 1, SYMBOL_CLASSNAME},
  {"double", 2, ARRAY_CLASSNAME},
  {"float", 0, FLOAT_CLASSNAME},
  {"float", 1, SYMBOL_CLASSNAME},
  {"float", 2, ARRAY_CLASSNAME},
  {"OBJECT", 0, OBJECT_CLASSNAME},
  {"OBJECT", 1, OBJECT_CLASSNAME},
  {"OBJECT", 2, OBJECT_CLASSNAME},
  {"_object", 0, OBJECT_CLASSNAME},
  {"_object", 1, OBJECT_CLASSNAME},
  {"_object", 2, OBJECT_CLASSNAME},
  {"void", 0, OBJECT_CLASSNAME},
  {"void", 1, OBJECT_CLASSNAME},
  {"void", 2, OBJECT_CLASSNAME},
  /*
   *  Types defined by C99.
   */
#if defined (__linux__)
  {"_IO_FILE", 0, "FileStream"},
  {"_IO_FILE", 1, "FileStream"},
  {"_IO_FILE", 2, "FileStream"},
#else
# if defined (__sparc__)
  {"__FILE", 0, "FileStream"},
  {"__FILE", 1, "FileStream"},
  {"__FILE", 2, "FileStream"},
# endif /* #if defined (__sparc__) */
#endif /* #if defined (__linux__) */
  {"FILE", 0, "FileStream"},
  {"FILE", 1, "FileStream"},
  {"FILE", 2, "FileStream"},
  {"", -1, ""}
};

char *basic_class_from_cvar (CVAR *c, int n_expr_derefs) {
  static char buf[MAXLABEL];
  char type_buf[MAXMSG];
  CVAR *type_alias;
  int i, derefs;

  if (!c || !IS_CVAR(c)) {
    _warning ("basic_class_from_cvar: Invalid CVAR, class defaulting to Integer.\n");
    strcpy (buf, INTEGER_CLASSNAME);
    return buf;
  }
    

  if (!strcmp (c -> type, "int") &&
      !strcmp (c -> qualifier, "long") &&
      !strcmp (c -> qualifier2, "long")) {
    strcpy (type_buf, "long long int");
  } else {
#ifdef __GNUC__
    if (is_gnu_extension_keyword (c -> type)) {
      strcpy (type_buf, c -> qualifier);
    } else {
      strcpy (type_buf, c -> type);
    }
#else
    strcpy (type_buf, c -> type);
#endif
  }

  *buf = 0;
  derefs = c -> n_derefs - n_expr_derefs;
  for (i = 0; __C_type_conv[i].n_derefs != -1; i++) {
    if (!strcmp (__C_type_conv[i].c_type_name, type_buf) &&
	(derefs == __C_type_conv[i].n_derefs)) 
      strcpy (buf, __C_type_conv[i].class_name);
  }
      
  if (*buf == 0) {
    if ((type_alias = get_typedef (c -> type)) != NULL) {
      strcpy (buf, basic_class_from_cvar (type_alias, n_expr_derefs));
    } else {
      _warning (__warn_unknown_C_type_msg (c -> type));
      strcpy (buf, INTEGER_CLASSNAME);
    }
  }

  return buf;
}

int is_translatable_basic_class (char *classname) {
  if (!strcmp (classname, CHARACTER_CLASSNAME) ||
      is_subclass_of (classname, CHARACTER_CLASSNAME)) {
    return TRUE;
  }

  if (!strcmp (classname, FLOAT_CLASSNAME) ||
      is_subclass_of (classname, FLOAT_CLASSNAME)) {
    return TRUE;
  }

  if (!strcmp (classname, INTEGER_CLASSNAME) ||
      is_subclass_of (classname, INTEGER_CLASSNAME)) {
    return TRUE;
  }

  if (!strcmp (classname, LONGINTEGER_CLASSNAME) ||
      is_subclass_of (classname, LONGINTEGER_CLASSNAME)) {
    return TRUE;
  }

  return FALSE;
}


