/* $Id: object.c,v 1.5 2008/10/29 22:47:01 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 <string.h>
#include <stdlib.h>
#include <ctype.h>
#include <errno.h>
#include "object.h"
#include "message.h"
#include "ctalk.h"
#include "lex.h"
#include "typeof.h"

extern I_PASS interpreter_pass;

extern CVAR *global_cvars;      /* Declared in rt_cvar.c. */
extern CFUNC *functions;

extern OBJECT *classes;         /* Declared in class.c. */
extern OBJECT *last_class;

static OBJECT *dictionary = NULL;      /* Object dictionary for globals. */

extern int fn_arg_parser_level; /* Declared in cvars.c. */

/* 
 *     Add an object to the dictionary or to the parser's local
 *     variable list.
 */

int add_object (OBJECT *o) {

  OBJECT *p;
  PARSER *r;

  /*
   *  Everything but global variables goes into the parser's
   *  local variable list.
   */

  if (THIS_FRAME -> scope != GLOBAL_VAR) {

    r = current_parser ();

    if (!r -> vars) {
      r -> vars = o;
      return SUCCESS;
    }

    for (p = r -> vars; p -> next; p = p -> next)
      ;

    p -> next = o;
    o -> prev = p;
    o -> next = NULL;

  } else {
    if (!dictionary) {
      dictionary = o;
      return SUCCESS;
    }

    for (p = dictionary; p -> next; p = p -> next)
      ;

    p -> next = o;
    o -> prev = p;
    o -> next = NULL;

  }

  return SUCCESS;
}

/*
 *   Delete objects that are local to a parser scope.
 */

void delete_local_objects (void) {

  OBJECT *t, *last;
  PARSER *p;

  p = current_parser ();    

  if (p -> vars) {
    for (last = p -> vars; last && last -> next; last = last -> next)
      ;

    while (last != p -> vars) {
      t = last -> prev;
      delete_object (last);
      if (!t || t == p -> vars)
	break;
      last = t;
    }
    delete_object (p -> vars);
    p -> vars = NULL;
  }

  if (p -> methodobjects) {
    for (last = p -> methodobjects; last && last -> next; last = last -> next)
      ;
    while (last != p -> methodobjects) {
      t = last -> prev;
      delete_object (last);
      if (!t || t == p -> methodobjects)
	break;
      last = t;
    }
    delete_object (p -> methodobjects);
    p -> methodobjects = NULL;
  }
}

/* 
 *   Delete global objects of a given scope.
 */

void delete_objects (int scope) {

  OBJECT *p;

  for (p = dictionary; p ; p = p -> next) {
    if (p -> scope == scope)
      delete_object (p);
  }
}

/*
 *  Retrieve a global object.  Get_object () calls this 
 *  function and get_local_object ().
 */

OBJECT *get_global_object (char *name, char *classname) {

  OBJECT *o;

  if (!dictionary)
    return NULL;

  for ( o = dictionary; ; o = o -> next) {
    if (classname) {
      if (!strcmp (o -> __o_name, name) &&
	  !strcmp (o -> __o_classname, classname))
	return o;
    } else {
      if (!strcmp (o -> __o_name, name))
	return o;
    }
    if (!o || !o -> next)
      break;
  }

  return NULL;
}

/* 
 *  Retrieve a local object.  This is essentially the same
 *  as the code in get_object (), so it should be refactored
 *  in a some point.
 */

OBJECT *get_local_object (char *name, char *classname) {

  OBJECT *o;
  int i, local_frame_ptr;

  if (!HAVE_FRAMES)
    return NULL;

  /*
   *  If looking for C fn args, find the parser that 
   *  called c_fn_args ().
   */
  if (interpreter_pass == c_fn_pass) {
    for (i = parser_ptr (); i <= MAXARGS; i++) {
      if (parser_at (i) -> vars) {
	for ( o = parser_at (i) -> vars; o; o = o -> next) {
	  if (classname) {
	    if (!strcmp (o -> __o_name, name) &&
		!strcmp (o -> __o_classname, classname))
	      return o;
	  } else {
	    if (!strcmp (o -> __o_name, name))
	      return o;
	  }
	}
      }
    }
  }

  /*
   *  If checking an expression, use the variables of 
   *  the parent parser.
   */
  if (EXPR_CHECK) {
    i = parser_ptr () + 1;
    if (!parser_at (i) -> vars)
      return NULL;
    for ( o = parser_at (i) -> vars; ; o = o -> next) {
      if (classname) {
	if (!strcmp (o -> __o_name, name) &&
	    !strcmp (o -> __o_classname, classname))
	  return o;
      } else {
	if (!strcmp (o -> __o_name, name))
	  return o;
      }
      if (!o || !o -> next)
	break;
    }
  }

  if (((local_frame_ptr = get_frame_pointer ()) == MAXARGS + 1) || 
      ((local_frame_ptr = get_frame_pointer ()) == ERROR) ||
      (frame_at (local_frame_ptr) == NULL))
    return NULL;

  if (THIS_FRAME -> scope == LOCAL_VAR) {
    if (current_parser () -> vars) {
      for ( o = current_parser () -> vars; ; o = o -> next) {
	if (classname) {
	  if (!strcmp (o -> __o_name, name) &&
	      !strcmp (o -> __o_classname, classname))
	    return o;
	} else {
	  if (!strcmp (o -> __o_name, name))
	    return o;
	}
	if (!o || !o -> next)
	  break;
      }
    }
  }

  return NULL;
}

OBJECT *get_instance_variable (char *name, char *classname, int warn) {
  OBJECT *o,
    *class_object;

  if (classname) {
    if ((class_object = get_class_object (classname)) == NULL) {
      if (warn)
	__ctalkExceptionInternal (message_stack_at (get_messageptr ()), 
				  undefined_class_x, classname);
      return NULL;
    }

    for (o = class_object -> instancevars; o; o = o -> next) {
      if (!strcmp (o -> __o_name, name))
	return o;
    }

  } else {
    for (class_object = classes; class_object; 
	 class_object = class_object -> next) {
      for (o = class_object -> instancevars; o; o = o -> next) {
	if (!strcmp (o -> __o_name, name))
	  return o;
      }
    }
  }

  return NULL;
}

OBJECT *find_class_variable (MESSAGE *m_orig, char *name) {
  OBJECT *class_object, *class_var_object;
  OBJECT *results[MAXARGS] = {0,};
  int results_ptr = 0;

  if (!classes) return NULL;

  for (class_object = classes; ;
       class_object = class_object -> next) {
    if (class_object && class_object -> classvars) {
      for (class_var_object = class_object -> classvars;
	   class_var_object;
	   class_var_object = class_var_object -> next) {
	if (!strcmp (name, class_var_object -> __o_name))
	  results[results_ptr++] = class_var_object;
      }
    }
    if (!class_object || !class_object -> next) break;
  }

  if (results_ptr > 1) {
    warning (m_orig, 
	     "Class variable %s is defined in multiple classes.\n", name);
  }

  return results[0];
}

OBJECT *get_class_variable (char *name, char *classname, int warn) {

  OBJECT *o,
    *class_object;

  if (classname) {
    if ((class_object = get_class_object (classname)) == NULL) {
      if (warn)
	__ctalkExceptionInternal (message_stack_at (get_messageptr ()), 
				  undefined_class_x, classname);
      return NULL;
    }

    for (o = class_object -> classvars; o; o = o -> next) {
      if (!strcmp (o -> __o_name, name))
	return o;
    }

  } else {
    for (class_object = classes; class_object; 
	 class_object = class_object -> next) {
      for (o = class_object -> classvars; o; o = o -> next) {
	if (!strcmp (o -> __o_name, name))
	  return o;
      }
    }
  }

  return NULL;
}

/* 
 *  Retrieve any object by name and/or classname. Calls the functions
 *  above for global and local objects.
 */

OBJECT *get_object (char *name, char *classname) {

  OBJECT *o;

  if ((o = get_class_variable (name, classname, FALSE)) != NULL)
    return o;
  if ((o = get_local_object (name, classname)) != NULL)
    return o;
  if ((o = get_global_object (name, classname)) != NULL)
    return o;

  return NULL;
}

/*
 *   Create a new object.  Objects generally change to 
 *   the receiver class in new_object, or are deleted
 *   if they are only used as arguments.
 */

OBJECT *create_object (char *class, char *name) {

  OBJECT *o;
  int frame_ptr;

  if ((o = (OBJECT *)calloc (1, sizeof (struct _object))) == NULL) {
    printf ("create_object: %s\n", strerror (errno));
    exit (1);
  }

  /*
   *  The longest a value should be is the length of a 
   *  literal, which is the same as the name when an
   *  argument object is created.
   */
  if ((o -> __o_value = (char *)calloc (strlen (NULLSTR) + 1, 
				     sizeof (char))) == NULL) {
    printf ("create_object: %s\n", strerror (errno));
    exit (1);
  }

  strcpy (o -> sig, "OBJECT");
  strcpy (o -> __o_classname, class);
  if (!strcmp (o -> __o_classname, "Class")) {
    o -> __o_class = o;
  } else {
    o -> __o_class = get_class_object (class);
  }
  strcpy (o -> __o_name, name);

  frame_ptr = parser_frame_ptr ();
  /* ERROR here means that parsing hasn't started, so
     the call is to define a default (global) object. */
  if (frame_ptr == ERROR)
    o -> scope = GLOBAL_VAR;
  else
    o -> scope = frame_at (frame_ptr) -> scope;

  strcpy (o -> __o_value, NULLSTR);

  return o;
}

/*
 *   Wrapper function to create an object with the, "value," instance
 *   variable initialized.
 */
OBJECT *create_object_init (char *class, char *superclass, 
			    char *name, char *value) {

  OBJECT *o, *o_value;

  o = create_object (class, name);
  o_value = create_object (class, "value");
  if (superclass) {
    strcpy (o -> __o_superclassname, superclass);
    strcpy (o_value -> __o_superclassname, superclass);
    o -> __o_superclass = 
      o_value -> __o_superclass = 
      get_class_object (superclass);
  }
  __ctalkSetObjectValue (o_value, value);
  o -> instancevars = o_value;
  
  return o;
}

int push_arg (METHOD *m, OBJECT *arg) {


  if (m -> n_params == m -> n_args)
    return ERROR;

  m -> args[m -> n_args++] = arg;

  return SUCCESS;
}

/*
 *  Delete an object, and its methods if it is a class object.
 */

void delete_object (OBJECT *o) {

  OBJECT *p;
  METHOD *m, *t;              /* Pointers to methods to be deleted. */
  OBJECT *r, *s;
  PARSER *parser;

  if (dictionary) {
    p = dictionary;
    while (p) {
      if (p == o) {
	if (p -> next)
	  p -> next -> prev = p -> prev;
	if (p -> prev)
	  p -> prev -> next = p -> next;
	if (p == dictionary) {
	  if (p -> next) {
	    dictionary = p -> next;
	  } else {
	    dictionary = NULL;
	  }
	}
      }
      p = p -> next;
    }
  }

  if ((parser = current_parser ()) != NULL) {
    for (p = parser -> methodobjects; p && p -> next; p = p -> next)
      ;
    if (p == o) {
      if (p == parser -> methodobjects)
	parser -> methodobjects = p -> next;
      if (p -> next) {
	p -> next -> prev = p -> prev;
      }
      if (p -> prev) {
	p -> prev -> next = p -> next;
      }
    }
  }

  if ((parser = current_parser ()) != NULL) {
    for (p = parser -> vars; p && p -> next; p = p -> next)
      ;
    if (p == o) {
      if (p == parser -> vars)
	parser -> vars = p -> next;
      if (p -> next) {
	p -> next -> prev = p -> prev;
      }
      if (p -> prev) {
	p -> prev -> next = p -> next;
      }
    }
  }

  /* Delete any methods and their source code. */
  /* This should not occur, because only class
     objects have methods, and they are not 
     deleted.  TO DO - Make sure that methods
     get deleted correctly anyway. */
     
  if (o -> instance_methods) {
    for (t = o -> instance_methods; t -> next; t = t -> next)
      ;

    while (t) {
      m = t -> prev;
      if (t -> src)
	free (t -> src);
      free (t);
      t = m;
    }
  }

  if (o -> instancevars) {
    for (r = o -> instancevars; r -> next; r = r -> next)
      ;
    while (r) {
      s = r -> prev;
      delete_object (r);
      r = s;
    }
  }

  if (o -> classvars) {
    for (r = o -> classvars; r -> next; r = r -> next)
      ;
    while (r) {
      s = r -> prev;
      delete_object (r);
      r = s;
    }
  }

  free (o -> __o_value);
  free (o);
}

/*
 *   Resolve self.  The function is called every time self appears,
 *   so we can check if it is a receiver or a C keyword.  That way
 *   "self" can be used as a receiver, and also expanded to the
 *   __ctalk_self_internal () function when it is used in 
 *   cases like, "self -> scope."
 */

extern OBJECT *rcvr_class_obj;   /* Declared in lib/rtnwmthd.c and */
                                 /* filled in by new_method ().  */

OBJECT *self_object (MESSAGE_STACK messages, int msg_ptr) {

  OBJECT_CONTEXT self_context;
  OBJECT *self_obj = NULL;       /* Avoid a warning. */
  int m_next_ptr;

  if (!strcmp (messages[msg_ptr] -> name, "self")) {

    /*
     *  TO DO - 1. This case should be handled as an exception.
     *          2. It might also be necessary to handle cases
     *             where, "self," appears in a method argument -
     *             myString subString 1, self length - 1;
     *             These cases are handled in method_args () 
     *             at the moment.
     *          3. Handle cases where the receiver is a 
     *             constant - e.g.;
     *             "This is a string." subString 1, self length - 1;
     *             This will require additions in resolve ().
     *
     *  Handle the case where, "self," appears in a function -
     *  that is, there is no receiver class.  If, "self," 
     *  appears as the receiver, generate a warning and 
     *  a run time call.  
     */

    if (!rcvr_class_obj) {
      if (interpreter_pass != method_pass) {
	warning (messages[msg_ptr], "\"Self,\" without receiver.");
	if (interpreter_pass == parsing_pass) {
	  if ((self_context = object_context (messages, msg_ptr)) ==
	      receiver_context) {
	    generate_self_call (msg_ptr);
	    ++messages[msg_ptr] -> evaled;
	    ++messages[msg_ptr] -> output;
	  }
	}
	return NULL;
      } else {
	error (messages[msg_ptr], "Parser error.");
      }
    }

    self_context = object_context (messages, msg_ptr);

    switch (self_context)
      {
      case c_context:
	if ((m_next_ptr = nextlangmsg (messages, msg_ptr)) != ERROR) {
	  METHOD *m;
	  if ((!METHOD_ARG_TERM_MSG_TYPE (messages[m_next_ptr])) &&
	      ((m = get_instance_method (messages[m_next_ptr],
				 rcvr_class_obj, messages[m_next_ptr] -> name,
			       FALSE)) != NULL)) {
	    self_obj = 
	      create_object_init (rcvr_class_obj -> __o_name, 
				  rcvr_class_obj -> __o_superclassname,
				  "self", NULLSTR);
	    instance_variables_from_class_definition (self_obj);
	    save_method_object (self_obj);
	  } else {
	    generate_self_call (msg_ptr);
	  }
	}
 	++messages[msg_ptr] -> evaled;
 	++messages[msg_ptr] -> output;
  	return self_obj;
 	break;
      case argument_context:
      case receiver_context:
	if (interpreter_pass == method_pass) {
	  /*
	   *   Create a local object of the method's class
	   *   and use that as the receiver.
	   */

 	  self_obj = 
	    create_object_init (rcvr_class_obj -> __o_name, 
				rcvr_class_obj -> __o_superclassname,
				"self", NULLSTR);
	  instance_variables_from_class_definition (self_obj);
 	  add_object (self_obj);
	  ++messages[msg_ptr] -> evaled;
	  ++messages[msg_ptr] -> output;
 	  return self_obj;
	}
 	break;
      default:
 	break;
      }
  }

  return NULL;
}

/*
 *  Determine if an object is actually a C expression.  Trim
 *  quotes around the name if it is a String object that is
 *  actually a char * variable.
 *  Check for array elements and struct members in
 *
 *    1. Local variables
 *    2. Global variables
 */

CVAR *is_c_variable (OBJECT *obj) {

  char name[MAXLABEL];
  CVAR *c,
    *c_mbr;
  int is_struct,
    is_array_element;

  if (is_printf_fmt (obj -> __o_name))
    return NULL;

  strcpy (name, obj -> __o_name);

  while (*name == '\"')
    TRIM_LITERAL (name);

  if (strstr (name, "->") || strstr (name, "."))
    is_struct = TRUE;
  else
    is_struct = FALSE;


  /*
   *  We have to check for printf format patterns here.
   */
  if (strstr (name, "[") && strstr (name, "]"))
    is_array_element = TRUE;
  else
    is_array_element = FALSE;

  for (c = current_parser () -> cvars; c; c = c -> next) {

    if (is_array_element) {
      if (strstr (name, c -> name))
	return c;
    }

    if (is_struct) {
      if (!strcmp (c -> type, "struct") ||
	  !strcmp (c -> type, "union") ||
	  !strcmp (c -> qualifier, "struct") ||
	  !strcmp (c -> qualifier, "union")) {
	if (strstr (name, c -> name)) {
	  for (c_mbr = c -> members; c_mbr; c_mbr = c_mbr -> next) {
	    if (strstr (name, c_mbr -> name))
	      return c_mbr;
	  }
	}
      }
    }

    if (!strcmp (c -> name, name))
      return c;

  }  /* for (c = current_parser () -> cvars; c; c = c -> next) */


  for (c = global_cvars; c; c = c -> next) {

    if (!IS_CVAR(c)) 
      break;

    if (is_array_element) {
      if (strstr (name, c -> name))
	return c;
    }

    if (is_struct) {
      if (!strcmp (c -> type, "struct") ||
	  !strcmp (c -> type, "union") ||
	  !strcmp (c -> qualifier, "struct") ||
	  !strcmp (c -> qualifier, "union")) {
	if (strstr (name, c -> name)) {
	  for (c_mbr = c -> members; c_mbr; c_mbr = c_mbr -> next) {
	    if (strstr (name, c_mbr -> name))
	      return c_mbr;
	  }
	}
      }
    }

    if (!strcmp (c -> name, name))
      return c;

  } /* for (c = global_cvars; c; c = c -> next) */

  return NULL;
}

CFUNC *is_c_fn (OBJECT *obj) {

  CFUNC *c;
  char *s, name[MAXLABEL];

  strcpy (name, obj -> __o_name);

  while (*name == '\"')
    TRIM_LITERAL (name);

  /*
   *   Trim the name at the first non-label character.
   */
  for (s = name; *s; s++)
    if (!isalnum ((int) *s) && *s != '_')
      break;
  *s = 0;

  for (c = functions; c; c = c -> next) {
    if (!strcmp (name, c -> decl))
      return c;
  }

  return NULL;

}

/*
 *  Return TRUE if a label is defined anywhere.
 *
 *  Check also for method labels without looking for 
 *  a METHODMSGLABEL token type, and parameter names.
 */

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

int label_is_defined (MESSAGE_STACK messages, int ptr) {

  OBJECT *tmp;
  MESSAGE *m;
  METHOD *method;
  int i;
  CVAR *c;

  m = messages[ptr];

  if (!strcmp (m -> name, "self") ||
      !strcmp (m -> name, "super"))
    return TRUE;

  if (((tmp = get_object (m -> name, NULL)) != NULL) ||
      ((tmp = get_class_object (m -> name)) != NULL))
    return TRUE;

  if (((c = get_local_var (messages[ptr] -> name)) != NULL) ||
      ((c = get_global_var (messages[ptr] -> name)) != NULL))
    return TRUE;

  if (is_struct_element (messages, ptr))
    return TRUE;

  if (is_instance_method (messages, ptr) ||
      is_class_method (messages, ptr))
    return TRUE;

  if ((interpreter_pass == method_pass) ||
      (interpreter_pass == expr_check)) {
    if (new_method_ptr < MAXARGS) {
      if ((method = new_methods[new_method_ptr + 1]) != NULL) {
	for (i = 0; i < method -> n_params; i++) {
	  if (!strcmp (method -> params[i] -> name, m -> name))
	    return TRUE;
	}
      }
    }
  }

  return FALSE;
}

OBJECT *constant_token_object (MESSAGE *m_tok) {

  OBJECT *arg_obj;

  switch (m_tok -> tokentype)
    {
    case LITERAL:
      arg_obj = 
	create_object_init (STRING_CLASSNAME, 
			    STRING_SUPERCLASSNAME,
			    m_tok -> name, m_tok -> name);
      break;
    case LITERAL_CHAR:
      arg_obj = 
	create_object_init (CHARACTER_CLASSNAME, 
			    CHARACTER_SUPERCLASSNAME,
			    m_tok -> name, m_tok -> name);
      break;
    case INTEGER:
    case LONG:
      arg_obj = 
	create_object_init (INTEGER_CLASSNAME, 
			    INTEGER_SUPERCLASSNAME,
			    m_tok -> name, m_tok -> name);
      break;
    case FLOAT:
      arg_obj = 
	create_object_init (FLOAT_CLASSNAME, 
			    FLOAT_SUPERCLASSNAME,
			    m_tok -> name, m_tok -> name);
      break;
    case LONGLONG:
      arg_obj = 
	create_object_init (LONGINTEGER_CLASSNAME, 
			    LONGINTEGER_SUPERCLASSNAME,
			    m_tok -> name, m_tok -> name);
      break;
    default:
      _warning ("Unknown token type in constant_token_object.\n");
      arg_obj = NULL;
    }
  return arg_obj;
}

/*
 *  Called by eval_arg (), when we don't know what to do
 *  with an argument.
 */
OBJECT *arg_expr_object (char *argbuf) {
  OBJECT *arg_object;
  arg_object = __ctalkCreateObject (argbuf, EXPR_CLASSNAME, NULL, ARG_VAR);
  strcpy (arg_object -> __o_superclassname, EXPR_SUPERCLASSNAME);
  return arg_object;
}

