/* $Id: cvars.c,v 1.10 2009/02/25 18:09:13 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 <errno.h>
#include <string.h>
#include <stddef.h>
#include <errno.h>
#include "object.h"
#include "message.h"
#include "ctalk.h"
#include "lex.h"
#include "typeof.h"
#include "cvar.h"

/*
 *  Prototype here so we don't have to include stdio.h in everything.
 */
FILE *fopen_tmp (char *, char *);
int fclose_tmp (FILE *);

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

extern char input_source_file[FILENAME_MAX];

extern CVAR *global_cvars;            /* Declared in rt_cvar.c.            */
extern CVAR *local_cvars;
extern CVAR *typedefs;
extern CVAR *typedefs_ptr;
extern CVAR *externs;
extern CVAR *externs_ptr;

HASHTAB declared_global_variables;
HASHTAB declared_typedefs;
HASHTAB declared_functions;
static HASHTAB enum_hash;

extern CVAR *struct_members,            /* Declared in cparse.c. */
  *struct_members_ptr;

extern STRUCT_DECL struct_decl[MAXARGS];
extern int struct_decl_ptr;

extern CVAR *fn_param_decls,
  *fn_param_decls_ptr;

static CVAR *incomplete_typedefs,
  *incomplete_typedefs_ptr;

MESSAGE *var_messages[N_MESSAGES + 1];
int var_messageptr;

void init_cvars (void) {
  var_messageptr = N_MESSAGES;
  global_cvars = local_cvars = typedefs = typedefs_ptr = 
    externs = externs_ptr = NULL;
  incomplete_typedefs = incomplete_typedefs_ptr = NULL;
  _new_hash (&declared_global_variables);
  _new_hash (&declared_typedefs);
  _new_hash (&declared_functions);
  _new_hash (&enum_hash);
}

int var_message_push (MESSAGE *m) {
  if (var_messageptr == 0) {
    _error ("Var_message_push: stack_overflow.");
    return ERROR;
  }

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

  var_messages[var_messageptr] = m;

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

  --var_messageptr;

  return var_messageptr + 1;
}

int get_var_messageptr (void) {
  return var_messageptr;
}

MESSAGE_STACK var_message_stack (void) {
  return var_messages;
}

MESSAGE *var_message_stack_at (int idx) {
  return var_messages[idx];
}

int get_params (void) {

  PARSER *p;
  CVAR param, 
    *c,
    *t;
  int param_ptr,
    i;

  param_ptr = get_param_ptr ();

  p = current_parser ();

  for (i = 0; i <= param_ptr; i++) {

    _new_cvar (CVARREF(c));

    param = get_param_n (i);

    if (*param.decl)
      strcpy (c -> decl, param.decl);
    if (*param.type)
      strcpy (c -> type, param.type);
    if (*param.qualifier)
      strcpy (c -> qualifier, param.qualifier);
    if (*param.qualifier2)
      strcpy (c -> qualifier2, param.qualifier2);
    if (*param.storage_class)
      strcpy (c -> storage_class, param.storage_class);
    if (*param.name)
      strcpy (c -> name, param.name);
    c -> n_derefs = param.n_derefs;
    c -> is_unsigned = param.is_unsigned;
    c -> scope = param.scope;

    if (!p -> cvars) {
      p -> cvars = c;
    } else {
      for (t = p -> cvars; ; t = t -> next) {
	if (!t -> next) break;
      }
      t -> next = c;
      c -> prev = t;
    }
  }

  return SUCCESS;
}

extern int return_is_unsigned,         /* Declared in cparse.c. */
  return_is_ptr,
  return_is_ptr_ptr,
  decl_is_ptr,
  decl_is_ptr_ptr,
  decl_is_prototype;
extern FN_DECLARATOR declarators[10]; 
extern int n_declarators;

#define DECLARATOR_IS_STORAGE_CLASS(attr) \
  (((attr) & CVAR_TYPE_CONST) || \
   ((attr) & CVAR_TYPE_EXTERN) || \
   ((attr) & CVAR_TYPE_INLINE) || \
   ((attr) & CVAR_TYPE_REGISTER) || \
   ((attr) & CVAR_TYPE_STATIC) || \
   ((attr) & CVAR_TYPE_VOLATILE))

int add_function (MESSAGE *orig) {

  CFUNC *c;
  int i, n_params, param_n;
  CVAR tmp_cvar, *param_cvar, *param_head = NULL; /* Avoid a warning. */

  if ((c = (CFUNC *)calloc (1, sizeof (CFUNC))) == NULL) 
    _error ("add_function (): %s\n.", strerror (errno));
  
  i = n_declarators;
  --i;
  /* Perform some type checking here. 
     TO DO - This should be factored out for vars as well.
   */
  if (!decl_is_prototype &&
      (is_c_data_type (declarators[i].name) ||
       get_typedef (declarators[i].name) ||
       is_c_storage_class (declarators[i].name)))
    warning (orig, "\"%s\" is not a valid function name.", declarators[i].name);
  strcpy (c -> decl, declarators[i].name);

  --i;
  if (i >= 0) {
    if (declarators[i].attr & CVAR_TYPE_UNSIGNED) {
      c -> is_unsigned = TRUE;
      c -> return_type_attrs |= CVAR_TYPE_UNSIGNED;
    } else {
      if (DECLARATOR_IS_STORAGE_CLASS(declarators[i].attr)) {
	warning (orig, "Return type of function \"%s\" defaults to int.",
		 declarators[i].name);
	strcpy (c -> return_type, "int");
	c -> return_type_attrs |= CVAR_TYPE_INT;
	strcpy (c -> storage_class, declarators[i].name);
	c -> return_type_attrs |= declarators[i].attr;
      } else {
	if (!IS_DEFINED_LABEL (declarators[i].name)) {
	  if (is_ctalk_keyword(declarators[i].name)) {
	    warning 
	      (orig, 
	       "Invalid keyword, \"%s,\" in function or method declaration.", 
	       declarators[i].name);
	  } else {
	    if (IS_CONSTRUCTOR_LABEL(declarators[i].name)) {
	      warning 
		(orig, 
		 "Invalid constructor, \"%s,\" in function or method declaration.", 
		 declarators[i].name);
	    } else {
	      warning 
		(orig, 
		 "Undefined label, \"%s,\" in function or method declaration.", 
		 declarators[i].name);
	    }
	  }
	}
	strcpy (c -> return_type, declarators[i].name);
	c -> return_type_attrs |= declarators[i].attr;
      }
    }
  }
  
  --i;
  if (i >= 0) {
    if (declarators[i].attr & CVAR_TYPE_UNSIGNED) {
      c -> is_unsigned = TRUE;
      c -> return_type_attrs |= CVAR_TYPE_UNSIGNED;
    } else {
      if (DECLARATOR_IS_STORAGE_CLASS(declarators[i].attr))
	strcpy (c -> storage_class, declarators[i].name);
      else
	strcpy (c -> qualifier_type, declarators[i].name);
      c -> return_type_attrs |= declarators[i].attr;
    }
  }

  --i;
  if (i >= 0) {
    if (declarators[i].attr & CVAR_TYPE_UNSIGNED) {
      c -> is_unsigned = TRUE;
      c -> return_type_attrs |= CVAR_TYPE_UNSIGNED;
    } else {
    /*   if (is_c_storage_class (declarators[i].name)) */
      if (DECLARATOR_IS_STORAGE_CLASS(declarators[i].attr))
	strcpy (c -> storage_class, declarators[i].name);
      else
	strcpy (c -> qualifier2_type, declarators[i].name);
      c -> return_type_attrs |= declarators[i].attr;
    }
  }

  --i;
  if (i >= 0) {
    if (declarators[i].attr & CVAR_TYPE_UNSIGNED) {
      c -> is_unsigned = TRUE;
      c -> return_type_attrs |= CVAR_TYPE_UNSIGNED;
    } else {
      if (!DECLARATOR_IS_STORAGE_CLASS(declarators[i].attr))
	warning (orig, "Unknown storage class, \"%s.\"", declarators[i].name);
      strcpy (c -> storage_class, declarators[i].name);
      c -> return_type_attrs |= declarators[i].attr;
    }
  }
  c -> is_ptr_decl = decl_is_ptr;
  c -> is_prototype_decl = decl_is_prototype;
  c -> return_derefs = ((return_is_ptr) ? 1 : ((return_is_ptr_ptr) ? 2 : 0));

  n_params = get_param_ptr ();
  for (param_n = 0; param_n <= n_params; param_n++) {
    _new_cvar (CVARREF(param_cvar));
    tmp_cvar = get_param_n (param_n);
    memcpy (param_cvar, &tmp_cvar, sizeof (CVAR));

    if (c -> params == NULL) {
      c -> params = param_head = param_cvar;
    } else {
      param_head -> next = param_cvar;
      param_cvar -> prev = param_head;
      param_head = param_cvar;
    }
  }

  _hash_put (declared_functions, (void *)c, c -> decl);

  return SUCCESS;

}

int var_cmp (MESSAGE *var_msg, CVAR *c1, CVAR *c2) {

  if (!memcmp ((void *)c1, (void *)c2, offsetof (CVAR, members)))
    return SUCCESS;

  /* If the names match, make sure that the redefinition isn't
     actually a type tag before issuing a warning, and we
     also aren't issuing warnings for aggregate type members. 

     However, we don't need, at this point, to have more than
     one CVAR registered per identifier tag, regardless of the
     variables' types, so return SUCCESS to indicate that the 
     tags' names would create duplicate entries.
  */
  if (!strcmp (c1 -> name, c2 -> name)) {
    if (!get_typedef (c1 -> name)) {
      if (strcmp (c1 -> type, "struct") &&
	  strcmp (c1 -> qualifier, "struct") &&
	  strcmp (c2 -> type, "struct") && 
	  strcmp (c2 -> qualifier, "struct"))
	warning (var_msg, "Redefinition of %s.", c2 -> name);
    }
    return SUCCESS;
  }

  return 1;
}

int var_cmp2 (CVAR *c1, CVAR *c2) {

  if (!c1 || !IS_CVAR(c1) || !c2 || !IS_CVAR(c2))
    return ERROR;

  if (!memcmp ((void *)c1, (void *)c2, offsetof (CVAR, members)))
    return SUCCESS;

  /* 
   *  If the names match, make sure that the redefinition isn't
   *  actually a type tag before issuing a warning, and we
   *  also aren't issuing warnings for aggregate type members. 
   *
   *  C99 says that tags of structs, unions, and enums, have
   *  a different name space than other identifiers.  If only
   *  one of the attributes specifies a tag, then add the
   *  identifier with the duplicate name.
   */
  if (!strcmp (c1 -> name, c2 -> name)) {

    if ((c1 -> attrs & CVAR_ATTR_STRUCT_DECL) &&
	(c2 -> attrs & CVAR_ATTR_STRUCT_DECL))
      return SUCCESS;
    else
      return ERROR;
  }

  return ERROR;
}

void delete_local_c_vars (void) {

  PARSER *p;
  CVAR *c, *c_prev;

  p = current_parser ();

  if (!p -> cvars)
    return;

  for (c = p -> cvars; c -> next; c = c -> next)
    ;
  
  while (c != local_cvars) {
    c_prev = c -> prev;
    _delete_cvar (c);
    c = c_prev;
  }
  _delete_cvar (c);
  p -> cvars = NULL;
  
}

int add_variable_from_cvar (CVAR *c) {

  int global_frame;
  PARSER *p;
  CVAR *t, *c1;

  if (((global_frame = is_global_frame ()) == FALSE) && /* Local frame */
      ((interpreter_pass != var_pass) &&      /* If recursing into class */
       (interpreter_pass != library_pass))) { /* lib and scope isn't global.*/
    /*
     *  Add the variable to the parser's variable list.
     */
    p = current_parser ();

    if (!p -> cvars) {
      p -> cvars = c;
    } else {
      for (t = p -> cvars; ; t = t -> next) {
 	if (!var_cmp2 (t, c)) {
 	  _delete_cvar (c);
 	  return ERROR;
 	}
	if (!t -> next) break;
      }
      t -> next = c;
      c -> prev = t;
    }

  } else {  /* Global frame (or no frame). */

    if (!global_cvars) {
      global_cvars = c;
      for (c1 = c; c1; c1 = c1 -> next) {
	_hash_put (declared_global_variables, (void *)c1, c1 -> name);
	if (c1 -> attrs & CVAR_ATTR_ENUM) {
	  _hash_put (enum_hash, (void *)c1, c1 -> name);
	}
      }
    } else {
      for (t = global_cvars; t; t = t -> next) {
	if (!(t -> attrs & CVAR_ATTR_ENUM)) {
	  if (!var_cmp2 (t, c)) {
	    /*
	     *  System specific here - GNU C predeclares a lot
	     *  of structs as incomplete types, so check if a 
	     *  struct definition should replace a struct 
	     *  declaration.
	     * 
	     *  Enums, however, can be declared without a tag, so
	     *  they need to be excluded them from this stuff 
	     *  here.
	     *
	     *  TO DO - We will need to see if that affects 
	     *  incomplete enum declarations.  This is 
	     *  basically equivalent to the previous 
	     *  revisions of parse_vars (), where we simply
	     *  added an enum to global_cvars regardless of whether 
	     *  it was declared previously as an incomplete
	     *  type.
	     */
#ifdef __GNUC__
	    if ((t -> attrs == CVAR_ATTR_STRUCT) && 
		(c -> attrs & CVAR_ATTR_STRUCT_DECL)) {
	      c -> next = t -> next;
	      c -> prev = t -> prev;
	      if (t -> prev) t -> prev -> next = c;
	      if (t -> next) t -> next -> prev = c;
	      _delete_cvar (t);
	    } else {
	      _delete_cvar (c);
	    }
#else
	    _delete_cvar (c);
#endif
	    return ERROR;
	  }
	}
  	if (!t -> next) break;
      }
      t -> next = c; 
      c -> prev = t;
      for (c1 = c; c1; c1 = c1 -> next) {
	_hash_put (declared_global_variables, (void *)c1, c1 -> name);
	if (c1 -> attrs & CVAR_ATTR_ENUM)
	  _hash_put (enum_hash, (void *)c1, c1 -> name);
      }
    }
  }
  return SUCCESS;
}

/*
 * Any typedef that isn't completed by the end of the statement
 * goes here, then is resolved at the end of the variable pass.
 */
int add_incomplete_type (MESSAGE_STACK messages,int keyword_ptr,int type_ptr) {

  CVAR *c;
  int i, stack_end;
  MESSAGE *m;
  char type_buf[MAXLABEL]; 
  char tag_buf[12][MAXLABEL] = {{""},};
  int tag_buf_idx;
  enum {
    incomplete_typedef_null,
    incomplete_typedef_keyword,
    incomplete_typedef_type,
    incomplete_typedef_tag
  } incomplete_typedef_state;

  stack_end = get_stack_top (messages);

  *type_buf = 0;
  tag_buf_idx = 0;
  for (i = keyword_ptr, incomplete_typedef_state = incomplete_typedef_null; 
       i > stack_end; --i) {

    m = messages[i];

    if (M_ISSPACE(m)) continue;

    switch (M_TOK(m))
      {
      case LABEL:
      case CTYPE:
	switch (incomplete_typedef_state)
	  {
	  case incomplete_typedef_null:
	    if (strcmp (M_NAME(m), "typedef")) {
	      warning (m, "Error in typedef statement.");
	      return ERROR;
	    }
	    incomplete_typedef_state = incomplete_typedef_keyword;
	    break;
	  case incomplete_typedef_keyword:
	    strcpy (type_buf, M_NAME(m));
	    incomplete_typedef_state = incomplete_typedef_type;
	    break;
	  case incomplete_typedef_type:
	    strcpy (tag_buf[tag_buf_idx++], M_NAME(m));
	    incomplete_typedef_state = incomplete_typedef_tag;
	    break;
	  case incomplete_typedef_tag:
#ifdef __GNUC__
	    if (is_gnu_extension_keyword (M_NAME(m))) {
	      int i2, attr_n_parens;
	      for (i2 = i, attr_n_parens = 0; i2 > stack_end; i2--) {
		switch (messages[i2]->tokentype)
		  {
		  case OPENPAREN:
		    ++attr_n_parens;
		    break;
		  case CLOSEPAREN:
		    if (--attr_n_parens == 0) {
		      i = i2;
		      continue;
		    }
		    break;
		  }
	      }
	    } else {
#endif
	      strcpy (tag_buf[tag_buf_idx++], M_NAME(m));
#ifdef __GNUC__
	    }
#endif
	  }
	break;
      case SEMICOLON:
	if (incomplete_typedef_state != incomplete_typedef_tag) {
	  warning (m, "Error in typedef statement.");
	  return ERROR;
	}
	_new_cvar (CVARREF(c));
	strcpy (c -> storage_class, "typedef");
	c -> attrs = CVAR_ATTR_TYPEDEF_INCOMPLETE;
	strcpy (c -> type, type_buf);
	strcpy (c -> name, tag_buf[--tag_buf_idx]);
	while (tag_buf_idx > 0) {
	  switch (--tag_buf_idx)
	    {
	    case 0:
	      strcpy (c -> qualifier, tag_buf[tag_buf_idx]);
	      break;
	    case 1:
	      strcpy (c -> qualifier2, tag_buf[tag_buf_idx]);
	      break;
	    case 2:
	      strcpy (c -> qualifier3, tag_buf[tag_buf_idx]);
	      break;
	    case 3:
	      strcpy (c -> qualifier4, tag_buf[tag_buf_idx]);
	      break;
	    default:
	      _warning ("add_incomplete_type: Too many qualifiers in typedef %s\n", c -> name);
	      break;
	    }
	}
	if (!incomplete_typedefs_ptr) {
	  incomplete_typedefs = incomplete_typedefs_ptr = c;
	} else {
	  incomplete_typedefs_ptr -> next = c;
	  c -> prev = incomplete_typedefs_ptr;
	  incomplete_typedefs_ptr = c;
	}
	return SUCCESS;
	break;
      }
  }

  return SUCCESS;
}

int is_incomplete_type (char *s) {
  CVAR *t;
  for (t = incomplete_typedefs; t; t = t -> next) {
    if (!strcmp (s, t -> name))
      return TRUE;
  }
  return FALSE;
}

static void __delete_incomplete_typedef (CVAR *t) {
  CVAR *tmp;
  if (t == incomplete_typedefs) {
    incomplete_typedefs = incomplete_typedefs->next;
    if (incomplete_typedefs) incomplete_typedefs -> prev = NULL;
  } 
  if (t == incomplete_typedefs_ptr) {
    incomplete_typedefs_ptr = incomplete_typedefs_ptr -> prev;
    if (incomplete_typedefs_ptr) { 
      incomplete_typedefs_ptr -> next = NULL;
    }
  }
  tmp = t;
  if (t -> next) t -> next -> prev = t -> prev;
  if (t -> prev) t -> prev -> next = t -> next;
  _delete_cvar (tmp);
}

/*
 *  Called at the end of the var pass. 
 *  TO DO - Does not have an incomplete typedef from
 *  another typedef.
 */
int resolve_incomplete_types (void) {

  CVAR *t, *n, *p, *var, *mbr, *mbr_ptr = NULL;

  for (t = incomplete_typedefs; t;) {
    /*
     *  SunOS says that typedef <type> <type> is valid, 
     *  even if incomplete.
     */
    if (!strcmp (t -> name, t -> type) ||
	!strcmp (t -> name, t -> qualifier)) {
      CVAR *tmp;
      t -> attrs |= CVAR_ATTR_TYPEDEF;
      if (t == incomplete_typedefs) 
	incomplete_typedefs = incomplete_typedefs->next;
      tmp = t;
      if (t -> next) t -> next -> prev = t -> prev;
      if (t -> prev) t -> prev -> next = t -> next;
      t = t -> next;
      tmp -> next = tmp -> prev = NULL;
      add_typedef_from_cvar (tmp);
    } else {
      if (((var = get_global_var (t -> name)) != NULL) ||
	  ((var = get_global_var (t -> qualifier)) != NULL)) {
	n= __ctalkCopyCVariable (t);
	for (p = var -> members; p; p = p -> next) {
	  mbr = __ctalkCopyCVariable (p);
	  if (! n -> members) {
	    n -> members = mbr_ptr = mbr;
	  } else {
	    mbr_ptr -> next = mbr;
	    mbr -> prev = mbr_ptr;
	    mbr_ptr = mbr_ptr -> next;
	  }
	}
	n -> attrs |= CVAR_ATTR_TYPEDEF;
	add_typedef_from_cvar (n);
	if (t -> next) {
	  t = t -> next;
	  __delete_incomplete_typedef (t->prev);
	} else {
	  __delete_incomplete_typedef (t);
	  return SUCCESS;
	}
      } else {
	t = t -> next;
      } 
    }
  }
  return SUCCESS;
}

int add_typedef_from_cvar (CVAR *c) {

  CVAR *t;

  for (t = c; t; t = t -> next) {
    t -> attrs |= CVAR_ATTR_TYPEDEF;
    generate_register_typedef_call (t);
    _hash_put (declared_typedefs, (void *)t, t -> name);
    if (t -> attrs & CVAR_ATTR_ENUM)
      _hash_put (enum_hash, (void *)t, t -> name);
    /*
     *  Enums with different types 
     *  (c -> type != enum ? c -> qualifier == enum) and 
     *  tag (c -> name) can be registered with both the type 
     *  and tag so far.
     */
    if ((t -> attrs & CVAR_ATTR_ENUM) &&
 	strcmp (c -> type, "enum")) {
      _hash_put (declared_typedefs, (void *)t, t -> type);
      _hash_put (enum_hash, (void *)t, t -> type);
    }
  }

  if (!typedefs) {
    typedefs = typedefs_ptr = c;
  } else {
    typedefs_ptr -> next = c;
    c -> prev = typedefs_ptr;
    typedefs_ptr = c;
  }

  /*
   *  If there's a list of cvars, set the list pointer
   *  to the last node.
   */
  while (typedefs_ptr -> next)
    typedefs_ptr = typedefs_ptr -> next;

  struct_def_from_typedef (c);

#ifdef __GNUC__
  gnuc_type_extensions (c);
#endif

  return SUCCESS;
}

int add_typedef_from_parser (void) {

  CVAR *c, *t;

  if (get_typedef (get_var (get_n_vars () - 1) -> name)) {
    _warning ("Redefinition of typedef \"%s.\"\n",
	      get_var (get_n_vars () - 1) -> name);
    return ERROR;
  }

  if ((c = parser_to_cvars ()) != NULL) {

    for (t = c; t; t = t -> next) {
      t -> attrs |= CVAR_ATTR_TYPEDEF;
      generate_register_typedef_call (t);
      _hash_put (declared_typedefs, (void *)t, t -> name);
      if (t -> attrs & CVAR_ATTR_ENUM)
	_hash_put (enum_hash, (void *)t, t -> name);
    }

     if (!typedefs) {
       typedefs = typedefs_ptr = c;
     } else {
       typedefs_ptr -> next = c;
       c -> prev = typedefs_ptr;
       typedefs_ptr = c;
     }
   while (typedefs_ptr -> next)
     typedefs_ptr = typedefs_ptr -> next;
   struct_def_from_typedef (c);
#ifdef __GNUC__
   gnuc_type_extensions (c);
#endif
  }
  return SUCCESS;
}

/*
 *  If we encounter a typedef like the following:
 * 
 *  typedef struct _s_type {
 *    ...
 *  } s_tag;
 *
 *  Then add a global struct definition as:
 *
 *  struct {
 *    ...
 *  } _s_type;
 *
 *  NOTE - C99 says that typedefs share the same namespace
 *  as variables.  So in the future if we add attributes
 *  to the CVAR type that specifies a typedef, we can
 *  simply check for a struct definition using the type
 *  or qualifier of the typedef instead of adding the
 *  struct definition separately using this function.
 *
 *  NOTE2 - This function only toggles the TYPEDEF
 *  attribute, so it can only be called with a 
 *  typedef declaration for the attributes of the
 *  definition to be set correctly
 */
int struct_def_from_typedef (CVAR *c) {
  CVAR *n, *n_mbr, *dup_member, *n_mbr_ptr = NULL;   /* Avoid a warning. */

  if ((!strcmp (c -> qualifier, "struct") ||
       !strcmp (c -> qualifier, "union")) &&
      (*c -> type != 0) &&
      c -> members) {
    _new_cvar (CVARREF(n));
    strcpy (n -> name, c -> type);
    strcpy (n -> type, c -> qualifier);
    for (n_mbr = c -> members; n_mbr; n_mbr = n_mbr -> next) {
      dup_member = __ctalkCopyCVariable (n_mbr);
      if (!n -> members) {
	n -> members = n_mbr_ptr = dup_member;
      } else {
	n_mbr_ptr -> next = dup_member;
	dup_member -> prev = n_mbr_ptr;
	n_mbr_ptr = dup_member;
      }
    }
    n -> attrs = c -> attrs ^ CVAR_ATTR_TYPEDEF;
    add_variable_from_cvar (n);
  }
  return SUCCESS;
}

/*
 *  Return a list of one or more cvars after calling
 *  is_c_var_declaration_msg ().
 */

CVAR *parser_to_cvars (void) {

  CVAR *c, *l_ptr;
  static CVAR *l;
  int i, j,
    n_vars,
    n_type_decls;

  n_vars = get_n_vars ();
  n_type_decls = get_type_decls_ptr ();

  for (i = 0, l = l_ptr = NULL; i < n_vars; i++) {

    _new_cvar (CVARREF(c));

    strcpy (c -> name, get_var (i) -> name);

    strcpy (c -> type, get_type_decl (n_type_decls - 1));
    for (j = 0; j <= n_type_decls - 2; j++) {
      if (j > 4)
	_warning ("Too many type qualifiers.\n");
      switch ((n_type_decls - 2) - j)
	{
 	case 0:
 	  strcpy (c -> qualifier, get_type_decl (j));
 	  break;
 	case 1:
 	  strcpy (c -> qualifier2, get_type_decl (j));
 	  break;
 	case 2:
 	  strcpy (c -> qualifier3, get_type_decl (j));
 	  break;
 	case 3:
 	  strcpy (c -> qualifier4, get_type_decl (j));
 	  break;
	}
    }

    c -> is_unsigned = (get_unsigned_declaration_val ()) ? True : False;
    if (get_var_storage_class ())
      strcpy (c -> storage_class, get_var_storage_class ());

    c -> attrs = get_cvar_attrs ();
    c -> type_attrs = get_cvar_type_attrs ();

    c -> n_derefs = get_var (i) -> n_derefs;

    /*
     *  We have to be careful here, because the C variable 
     *  parser doesn't know the difference between a 
     *  declaration like "struct _s {... }s;" and 
     *  "struct _s s;"  In cparse.c, we only store struct
     *  members in the struct_decl[] stack, and then
     *  set the global struct_members to the member cvars
     *  when returning from parsing an inner struct.
     *  This also makes it difficult, because every time
     *  we call is_c_var_declaration_msg3 (), we have to
     *  check additionally for a nested struct when 
     *  retrieving the cvar.
     */
    if (VAR_IS_STRUCT (c)) {
      c -> members = struct_members;
      struct_members = NULL;
    } else {
      if ((c -> attrs & CVAR_ATTR_FN_DECL) ||
	  (c -> attrs & CVAR_ATTR_FN_PTR_DECL) ||
	  (c -> attrs & CVAR_ATTR_FN_PROTOTYPE)) {
	if (!(c -> attrs & CVAR_ATTR_FN_NO_PARAMS)) {
	  c -> params = fn_param_decls;
	  fn_param_decls = fn_param_decls_ptr = NULL;
	}
      }
    }

    if (!l) {
      l = l_ptr = c;
    } else {
      l_ptr -> next = c;
      c -> prev = l_ptr;
      l_ptr = c;
    }

  }   /* for (i = 0, l = l_ptr = NULL; i < n_vars; i++) */

  return l;
}

/* 
 *  TO DO - Long longs, doubles, and long doubles do not
 *  align with ints and longs.  Test for promoting them.
 *
 *  Handle doubles and long doubles.
 */

int match_type (VAL *val1, VAL *val2) {

  int retval = ERROR;

  if (val1 -> type == val2 -> type) {
    retval = SUCCESS;
  } else {

    switch (val1 -> type) 
      {
      case INTEGER_T:
	switch (val2 -> type)
	  {
	  case LONG_T:
	    val1 -> type = LONG_T;
	    retval = SUCCESS;
	    break;
	  case LONGLONG_T:
	    val1 -> type = LONGLONG_T;
	    val1 -> value.ll = (long long) val1 -> value.i;
	    retval = SUCCESS;
	    break;
	  }
	break;
      case LONG_T:
	switch (val2 -> type)
	  {
	  case INTEGER_T:
	    val2 -> type = LONG_T;
	    retval = SUCCESS;
	    break;
	  case LONGLONG_T:
	    val1 -> type = LONGLONG_T;
	    val1 -> value.ll = (long long) val1 -> value.l;
	    retval = SUCCESS;
	    break;
	  }
	break;
      case LONGLONG_T:
	switch (val2 -> type)
	  {
	  case INTEGER_T:
	    val2 -> type = LONGLONG_T;
	    val2 -> value.ll = (long long) val2 -> value.i;
	    retval = SUCCESS;
	    break;
	  case LONG_T:
	    val2 -> type = LONGLONG_T;
	    val2 -> value.ll = (long long) val2 -> value.l;
	    retval = SUCCESS;
	    break;
	  }
	break;
      default:
	_warning ("Unimplemented type %d in match_type.", val1 -> type);
	break;
      }
  }
  

  return retval;
}

/*
 *  Note that structs that are declared with a type and a tag
 *  have the attributes CVAR_ATTR_STRUCT|CVAR_ATTR_STRUCT_DECL.
 *  If that changes in the future, the function may need to 
 *  check each attribute separately.
 */

#define CVAR_STRUCT_TYPE_TAG_ATTR (CVAR_ATTR_STRUCT|CVAR_ATTR_STRUCT_DECL)

CVAR *get_local_var (char *s) {

  PARSER *p;
  CVAR *c;

  if ((interpreter_pass == preprocessing_pass) ||
      (interpreter_pass == var_pass))
    return (CVAR *)NULL;

  if ((interpreter_pass == c_fn_pass) ||
      (interpreter_pass == expr_check)) {
    int parser_lvl;
    for (parser_lvl = parser_ptr () + 1; parser_lvl <= 512; parser_lvl++) {
      p = parser_at (parser_lvl);
      for (c = p -> cvars; c; c = c -> next) {
	if (c -> attrs & CVAR_STRUCT_TYPE_TAG_ATTR) {
	  if (!strcmp (s, c -> type))
	    return c;
	}
	if (!strcmp (s, c -> name))
	  return c;
      }
    }
    return NULL;
  } else {
    p = current_parser ();
  }

  if (!p -> cvars)
    return NULL;

  for (c = p -> cvars; c; c = c -> next) {
    if (c -> attrs & CVAR_STRUCT_TYPE_TAG_ATTR) {
      if (!strcmp (s, c -> type))
	return c;
    }
    if (!strcmp (s, c -> name))
      return c;
  }

  return NULL;
}

CVAR *get_local_struct_defn (char *s) {

  PARSER *p;
  CVAR *c;

  if ((interpreter_pass == preprocessing_pass) ||
      (interpreter_pass == var_pass))
    return (CVAR *)NULL;

  if ((interpreter_pass == c_fn_pass) ||
      (interpreter_pass == expr_check)) {
    int parser_lvl;
    for (parser_lvl = parser_ptr () + 1; parser_lvl <= 512; parser_lvl++) {
      p = parser_at (parser_lvl);
      for (c = p -> cvars; c; c = c -> next) {
	if (c -> attrs & CVAR_ATTR_STRUCT_DECL) {
	  if ((!strcmp (s, c -> type) || !strcmp (s, c -> name)) &&
	      c -> name)
	    return c;
	}
      }
    }
    return NULL;
  } else {
    p = current_parser ();
  }

  if (!p -> cvars)
    return NULL;

  for (c = p -> cvars; c; c = c -> next) {
    if (c -> attrs & CVAR_ATTR_STRUCT_DECL) {
      if ((!strcmp (s, c -> type) || !strcmp (s, c -> name)) &&
	  c -> members)
	return c;
    }
  }

  return NULL;
}

/*
 *  TO DO - Check for members of declarations without tags; e.g., 
 *  enums.
 */

CVAR *get_global_var (char *s) {
  CVAR *c;
  if (!s || *s == '\0')
    return NULL;

  if (!global_cvars) {
    return NULL;
  } else {
    if ((c = (CVAR *)_hash_get (declared_global_variables, s)) != NULL){
      return c;
    }
  }

  if (!global_var_is_declared (s)) return NULL;

  for (c = global_cvars; c; c = c -> next) {
    if (c -> attrs & CVAR_ATTR_STRUCT) {
      if (!strcmp (s, c -> name) ||
 	  !strcmp (s, c -> type) ||
 	  !strcmp (s, c -> qualifier))
 	return c;
    } else {
      if (!strcmp (s, c -> name))
 	return c;
    }
  }

  return NULL;
}

CVAR *get_global_struct_defn (char *s) {
  CVAR *c;

  for (c = global_cvars; c; c = c -> next) {
    if (c -> attrs & CVAR_ATTR_STRUCT_DECL) {
      if ((!strcmp (s, c -> name) ||
	   !strcmp (s, c -> type)) &&
	  c -> members)
 	return c;
    }
  }

  return NULL;
}

int global_var_is_declared (char *name) {

  if (_hash_get (declared_global_variables, name))
    return TRUE;
  else
    return FALSE;

}

CFUNC *get_function (char *s) {
  CFUNC *cfn;
  if ((cfn = (CFUNC *)_hash_get (declared_functions, s)) != NULL)
    return cfn;
  return NULL;
}

CVAR *get_typedef (char *s) {
  CVAR *c;
  if ((c = (CVAR *)_hash_get (declared_typedefs, s)) != NULL) {
    return c;
  } else {
    /*
     *  This is still needed.
     */
    for (c = typedefs; c; c = c -> next) {
      if (!strcmp (c -> type, s))
 	return c;
    }
  }
  return NULL;
}

int typedef_is_declared (char *name) {
  return ((get_typedef (name)) ? TRUE : FALSE);
}

int is_c_derived_type (char *s) {

  if (!strcmp (s, "FILE")) {
    CVAR *__c, *__c1;
    __c = _hash_get (declared_typedefs, "__FILE");
    __c1 = _hash_get (declared_typedefs, "_FILE");
  }

  if (_hash_get (declared_typedefs, s))
    return TRUE;
  else
    return FALSE;
}

void dump_typedefs (MESSAGE *orig) {
  CVAR *c;
  char buf[MAXMSG];
  int i;

  _hash_all_initialize ();
  while ((c = (CVAR *)_hash_all (declared_typedefs)) != NULL) {
    strcpy (buf, "typedef ");
    if (c -> is_unsigned)
      sprintf (buf, "%s %s", buf, "unsigned");
    if (*c -> qualifier2)
      sprintf (buf, "%s %s", buf, c->qualifier2);
     if (*c -> qualifier)
       sprintf (buf, "%s %s", buf, c->qualifier);
     if (*c -> type)
       sprintf (buf, "%s %s", buf, c->type);
     for (i = 0; i < c -> n_derefs; ++i)
       sprintf (buf, "%s %s", buf, "*");
     if (*c -> name)
       sprintf (buf, "%s %s", buf, c->name);
     warning (orig, "%s.", buf);
  }
}

static int old_input_size = 0;
static char *input = NULL;

int parse_vars (char *tmpname) {

  int i, tmpsize, end_ptr;
  MESSAGE *m;
  char tmp_src_file[FILENAME_MAX];
  CVAR *c;

  strcpy (tmp_src_file, input_source_file);

  if (!file_exists (tmpname))
    _error ("%s: %s.", tmpname, strerror (errno));
  
  tmpsize = file_size (tmpname);

  if (tmpsize > old_input_size) {
    old_input_size = tmpsize + 1;
    if ((input = (char *)realloc (input, tmpsize + 1)) == NULL)
      _error ("parse_vars: %s.", strerror (errno));
  }
  memset ((void *)input, 0, old_input_size);

  read_file (input, tmpname);

  end_ptr = tokenize (var_message_push, input);

  for (i = N_MESSAGES; i >= end_ptr; i--) {

    m = var_messages[i];

    if (M_ISSPACE (m)) continue;

    switch (m -> tokentype)
      {
      case PREPROCESS:
	if (var_message_stack_at (i-2) -> tokentype == INTEGER)
	  set_line_info (var_message_stack (), i, end_ptr);
	break;
      case LABEL:
	if (is_gnu_extension_keyword (m -> name)) {
	  if (warn_extension_opt) {
	    warning (m, "Compiler extension %s.", m -> name);
	  }
	  continue;
	}

	if (is_ctalk_keyword (m -> name)) {
	  i = find_declaration_end (var_message_stack (), i); 
	  continue;
	}

	if (!strcmp (m -> name, "enum")) {
	  CVAR *c;
	  if ((c = enum_decl (var_message_stack (), i)) != NULL)
	    add_variable_from_cvar (c);
	  i = find_declaration_end (var_message_stack (), i);
	} else {
	  if (!strcmp (m -> name, "typedef")) {
	    typedef_var (var_message_stack (), i);
	    i = find_declaration_end (var_message_stack (), i);
	  } else {
	    if (!strcmp (m -> name, "extern")) {
	      extern_declaration (var_message_stack (), i);
	      i = find_declaration_end (var_message_stack (), i);
	    } else {
	      if (is_c_fn_declaration_msg (var_message_stack (), i)) {
		i = find_function_close (var_message_stack (), i);
		add_function (m);
	      } else {
		if (is_c_function_prototype_declaration (var_message_stack (), i)) {
		  decl_is_prototype = TRUE;
		  i = find_declaration_end (var_message_stack (), i);
		  add_function (m);
		} else {
		  if (is_c_var_declaration_msg (var_message_stack (), i, FALSE)) {
		    if ((c = parser_to_cvars ()) != NULL)
		      validate_type_declarations (c);
		      add_variable_from_cvar (c);
		  }
		  i = find_declaration_end (var_message_stack (), i);
		}
	      }
	    }
	  } /* (!strcmp (m -> name, "typedef")) */
	} /* (!strcmp (m -> name, "enum")) */
	break;
      default:
	break;
      } /* switch */
  } /* for (i = N_MESSAGES... */


#ifdef DEBUG_TYPEDEFS
  dump_typedefs (var_message_stack_at (N_MESSAGES));
  exit (1);
#endif

  DELETE_MESSAGES(var_messages,var_messageptr,N_MESSAGES)

  strcpy (input_source_file, tmp_src_file);

  return SUCCESS;
}

char *var_tmp_ofn (char *infile_path) {
  static char tmp_ofn[FILENAME_MAX];
  char basename[FILENAME_MAX];
  char *r;

  if ((r = strrchr (infile_path, '/')) != NULL) {
    strcpy (basename, r + 1);
  } else {
    strcpy (basename, infile_path);
  }
  if ((r = strchr (basename, '.')) != NULL)
    *r = '\0';
  strcpy (tmp_ofn, __tempname (P_tmpdir, basename));
  return tmp_ofn;
}

int parse_vars_and_save (char *tmp_infile, char *tmp_outfile) {

  int i, tmpsize, end_ptr, output_idx;
  MESSAGE *m;
  char tmp_src_file[FILENAME_MAX];
  CVAR *c;
  FILE *__f;

  strcpy (tmp_src_file, input_source_file);

  if (!file_exists (tmp_infile))
    _error ("%s: %s.", tmp_infile, strerror (errno));
  
  tmpsize = file_size (tmp_infile);

  if (tmpsize > old_input_size) {
    old_input_size = tmpsize + 1;
    if ((input = (char *)realloc (input, tmpsize + 1)) == NULL)
      _error ("parse_vars_and_save: %s.", strerror (errno));
  }
  memset ((void *)input, 0, old_input_size);

  read_file (input, tmp_infile);

  end_ptr = tokenize (var_message_push, input);

  for (i = N_MESSAGES; i >= end_ptr; i--) {

    m = var_messages[i];

    if (M_ISSPACE (m)) continue;

    switch (m -> tokentype)
      {
      case PREPROCESS:
	if (var_message_stack_at (i-2) -> tokentype == INTEGER)
	  set_line_info (var_message_stack (), i, end_ptr);
	break;
      case LABEL:
	if (is_gnu_extension_keyword (m -> name)) {
	  if (warn_extension_opt) {
	    warning (m, "Compiler extension %s.", m -> name);
	  }
	  continue;
	}

	if (is_ctalk_keyword (m -> name)) {
	  i = find_declaration_end (var_message_stack (), i); 
	  continue;
	}

	if (!strcmp (m -> name, "enum")) {
	  CVAR *c;
	  if ((c = enum_decl (var_message_stack (), i)) != NULL)
	    add_variable_from_cvar (c);
	  i = find_declaration_end (var_message_stack (), i);
	} else {
	  if (!strcmp (m -> name, "typedef")) {
	    typedef_var (var_message_stack (), i);
	    i = find_declaration_end (var_message_stack (), i);
	  } else {
	    if (!strcmp (m -> name, "extern")) {
	      extern_declaration (var_message_stack (), i);
	      i = find_declaration_end (var_message_stack (), i);
	    } else {
	      if (is_c_fn_declaration_msg (var_message_stack (), i)) {
		i = find_function_close (var_message_stack (), i);
		add_function (m);
	      } else {
		if (is_c_function_prototype_declaration (var_message_stack (), i)) {
		  decl_is_prototype = TRUE;
		  i = find_declaration_end (var_message_stack (), i);
		  add_function (m);
		} else {
		  if (is_c_var_declaration_msg (var_message_stack (), i, FALSE)) {
		    if ((c = parser_to_cvars ()) != NULL)
		      validate_type_declarations (c);
		      add_variable_from_cvar (c);
		  }
		  i = find_declaration_end (var_message_stack (), i);
		}
	      }
	    }
	  } /* (!strcmp (m -> name, "typedef")) */
	} /* (!strcmp (m -> name, "enum")) */
	break;
      default:
	break;
      } /* switch */
  } /* for (i = N_MESSAGES... */


#ifdef DEBUG_TYPEDEFS
  dump_typedefs (var_message_stack_at (N_MESSAGES));
  exit (1);
#endif

  __f = fopen_tmp (tmp_outfile, "w");
  for (output_idx = N_MESSAGES; output_idx > end_ptr; output_idx--) {
    fputs (M_NAME(var_messages[output_idx]), __f); 
  }
  fputs ("\n", __f); 
  fclose_tmp (__f);

  DELETE_MESSAGES(var_messages,var_messageptr,N_MESSAGES)

  strcpy (input_source_file, tmp_src_file);

  return SUCCESS;
}

/*
 *  If we have a constant expression, then check if the next message
 *  is a label. 
 */

OBJECT *c_constant_object (MESSAGE_STACK messages, int msg_ptr) {

  OBJECT *o = NULL;       /* Avoid a warning. */
  MESSAGE *m, *m_next;
  int m_next_ptr;
  
  if ((m_next_ptr = nextlangmsg (messages, msg_ptr)) == ERROR)
    return NULL;

  if (M_TOK(messages[m_next_ptr]) != LABEL)
    return NULL;

  m = messages[msg_ptr];
  m_next = messages[m_next_ptr];

  switch (m -> tokentype)
    {
    case LITERAL:
      o = create_object (STRING_CLASSNAME, m -> name);
      strcpy (o -> __o_superclassname, STRING_SUPERCLASSNAME);
      break;
    case LITERAL_CHAR:
      o = create_object (CHARACTER_CLASSNAME, m -> name);
      strcpy (o -> __o_superclassname, CHARACTER_SUPERCLASSNAME);
      break;
    case INTEGER:
    case LONG:
      o = create_object (INTEGER_CLASSNAME, m -> name);
      strcpy (o -> __o_superclassname, INTEGER_SUPERCLASSNAME);
      break;
    case LONGLONG:
      o = create_object (LONGINTEGER_CLASSNAME, m -> name);
      strcpy (o -> __o_superclassname, LONGINTEGER_SUPERCLASSNAME);
      break;
    case FLOAT:
      o = create_object (FLOAT_CLASSNAME, m -> name);
      strcpy (o -> __o_superclassname, FLOAT_SUPERCLASSNAME);
      break;
    }

  o -> scope = RECEIVER_VAR;
  m -> obj = o;
  m_next -> receiver_obj = o;
  m_next -> tokentype = METHODMSGLABEL;
  m_next -> receiver_msg = m;
  return o;

}

/*
 *  Resolve C function args.  This means we have to use the parser
 *  stack in order to call resolve ().  Called by method_args () when
 *  we encounter a C function as an argument.  Arg_object is
 *  the CFunction object in an argument. 
 */

int fn_arg_parser_level;
static PENDING_FN_ARG *fn_arg_list,
  *fn_arg_ptr;

int c_fn_args (MESSAGE_STACK messages, OBJECT *arg_object, 
	       int method_msg_ptr) {

  int fn_ptr,
    arg_start_ptr,
    arg_end_ptr,
    stack_end_ptr,
    i,
    retval;
  I_PASS prev_i_pass;
  char buf[MAXMSG],
    output_buf[MAXMSG];
  PENDING_FN_ARG *p;

  /*
   *  Perform some checking here.
   */

  retval = SUCCESS;

  if ((fn_ptr = nextlangmsg (message_stack (), method_msg_ptr)) == ERROR)
    error (messages[method_msg_ptr], "Parser error.");

  if (strncmp (arg_object -> __o_name, messages[fn_ptr] -> name,
	       strlen (messages[fn_ptr] -> name))) {
    if ((fn_ptr = fn_is_arg (arg_object, messages, fn_ptr)) == 0) {
      error (messages[method_msg_ptr], "Parser error.");
    } else {
      retval = FN_IS_ARG_RETURN_VAL;
    }
  }

  if ((arg_start_ptr = nextlangmsg (messages, fn_ptr)) 
      == ERROR)
    error (messages[method_msg_ptr], "Parser error.");

  stack_end_ptr = get_stack_top (messages);

  if ((arg_end_ptr = match_paren (messages, arg_start_ptr,
				  stack_end_ptr)) == ERROR)
    error (messages[method_msg_ptr], "Parser error.");

  if ((messages[arg_start_ptr] -> tokentype != OPENPAREN) ||
      (messages[arg_end_ptr] -> tokentype != CLOSEPAREN))
    error (messages[method_msg_ptr], "Parser error.");

  /*
   *  Return if the argument list is empty.
   */
  if ((i = nextlangmsg (messages, arg_start_ptr)) == arg_end_ptr)
    return retval;

  prev_i_pass = interpreter_pass;
  interpreter_pass = c_fn_pass;

  strcpy (buf, collect_tokens (messages, arg_start_ptr, arg_end_ptr));

  fn_arg_list = fn_arg_ptr = NULL;

  fn_arg_parser_level = current_parser () -> level;

  parse (buf, (long long) strlen (buf));

  interpreter_pass = prev_i_pass;

  /*
   *  Substitute the fn_arg calls in the arg_object.
   */

  for (i = fn_ptr, *output_buf = 0; i >= arg_end_ptr; i--) {
    PENDING_FN_ARG *p2;
    for (p = fn_arg_list, p2 = NULL; p; p = p -> next) {
      if (!strcmp (p -> tok, messages[i] -> name))
	p2 = p;
    }

    if (p2)
      strcat (output_buf, p2 -> stmt);
    else
      strcat (output_buf, messages[i] -> name);

    /*
     *  Mark the messages as evaled and output here
     *  in case the function is called from somewhere 
     *  besides method_args ();
     */
  }

  __ctalkSetObjectName (arg_object, output_buf);
  __ctalkSetObjectValue (arg_object, output_buf);

  return retval;
}

void buffer_fn_args (PENDING *p, char *tok) {

  /*
   *  Duplicate the pending statement so fileout () et al. can
   *  delete the original pending statement.
   */
  PENDING_FN_ARG *p_dup;

  if ((p_dup = (PENDING_FN_ARG *) calloc (1, sizeof (struct _pending_fn_arg)))
      == NULL)
    _error ("buffer_fn_args: %s.\n", strerror (errno));

  strcpy (p_dup -> stmt, p -> stmt);
  strcpy (p_dup -> tok, tok);
  p_dup -> n = p -> n;

  if (!fn_arg_list) {
    fn_arg_list = fn_arg_ptr = p_dup;
  } else {
    fn_arg_ptr -> next = p_dup;
    p_dup -> prev = fn_arg_ptr;
    fn_arg_ptr = p_dup;
  }
}

/*
 *  Call with any label in the struct.  On exit the start
 *  and end stack indexes are in struct_element_start and
 *  struct_element_end.  To retrieve the struct CVAR, call
 *  get_struct_from_member () next.
 */

static int struct_element_start;
static int struct_element_end;
static char struct_labels[MAXARGS][MAXLABEL];
static int struct_label_ptr;

int is_struct_element (MESSAGE_STACK messages, int ptr) {

  int lookahead,
    lookback,
    stack_begin,
    stack_end,
    last_token = ptr,       /* Avoid warnings. */
    prev_token = ptr,
    non_struct_token,
    i;
  MESSAGE *m;

  stack_begin = stack_start (messages);
  stack_end = get_stack_top (messages);

  for (lookahead = ptr - 1, non_struct_token = FALSE, 
	 struct_element_end = ptr; 
       (lookahead >= stack_end) && (non_struct_token == FALSE); 
       lookahead--) {

    m = messages[lookahead];

    if (!m || !IS_MESSAGE (m))
      break;

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

    switch (m -> tokentype)
      {
      case LABEL:
	if (prev_token != DEREF && prev_token != PERIOD)
	  non_struct_token = TRUE;
	else
	  struct_element_end = lookahead;
	break;
      case DEREF:
      case PERIOD:
      case WHITESPACE:
      case NEWLINE:
	break;
      default:
	non_struct_token = TRUE;
	break;
      }
    prev_token = m -> tokentype;
  }

  for (lookback = ptr + 1, non_struct_token = FALSE, 
	 struct_element_start = ptr; 
       (lookback <= stack_begin) && (non_struct_token == FALSE); 
       lookback++) {

    m = messages[lookback];

    if (!m || !IS_MESSAGE (m))
      break;

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

    switch (m -> tokentype)
      {
      case LABEL:
	if (last_token != DEREF && last_token != PERIOD)
	  non_struct_token = TRUE;
	else
	  struct_element_start = lookback;
	break;
      case DEREF:
      case PERIOD:
      case WHITESPACE:
      case NEWLINE:
	break;
      default:
	non_struct_token = TRUE;
	break;
      }
    last_token = m -> tokentype;
  }

  for (i = struct_element_start, struct_label_ptr = 0; 
       i >= struct_element_end; i--) {
    m = messages[i];
    if (m -> tokentype == LABEL)
      strcpy (struct_labels[struct_label_ptr++], m -> name);
  }

  if (struct_label_ptr < 1)
    return FALSE;

  if (get_struct_from_member ())
    return TRUE;

  return FALSE;
}

CVAR *get_struct_from_member (void) {

  CVAR *c, *c_mbr;
  int i;

  if (((c = get_local_var (struct_labels[0])) != NULL) ||
      ((c = get_global_var (struct_labels[0])) != NULL)) {
    for (i = 1; i < struct_label_ptr; i++) {
      for (c_mbr = c -> members; c_mbr; c_mbr = c_mbr -> next) {
	if (!strcmp (struct_labels[i], c_mbr -> name))
	  break;
      }
      if (c_mbr == NULL)
	return NULL;
    }
    return c;
  }
  return NULL;
}

int is_enum_member (char *s) {

  CVAR *var, *mbr;

  _hash_all_initialize ();
  while ((var = (CVAR *)_hash_all (enum_hash)) != NULL) {
    for (mbr = var -> members; mbr; mbr = mbr -> next) {
      if (!strcmp (mbr -> name, s))
	return TRUE;
    }
  }

  if (HAVE_FRAMES) {
    for (var = current_parser () -> cvars; var; var = var -> next) {
      if (var -> attrs & CVAR_ATTR_ENUM) {
	for (mbr = var -> members; mbr; mbr = mbr -> next) {
	  if (!strcmp (mbr -> name, s))
	    return TRUE;
	}
      }
    }
  }

  return FALSE;
}

static int scan_struct_members (CVAR *c, char *s) {
  CVAR *mbr;
  for (mbr = c -> members; mbr; mbr = mbr -> next) {
    if (mbr -> members) {
      if (scan_struct_members (mbr, s))
	return TRUE;
    }
    /*
     *  Also check struct types for labels.
     */
    if (VAR_IS_STRUCT (mbr))
      if (!strcmp (mbr -> type, s) ||
	  !strcmp (mbr -> qualifier, s) ||
	  !strcmp (mbr -> qualifier2, s))
	return TRUE;
    if (!strcmp (mbr -> name, s))
      return TRUE;
  }
  return FALSE;
}

int is_struct_member (char *s) {

  CVAR *var;


  _hash_all_initialize ();
  while ((var = (CVAR *)_hash_all (declared_global_variables)) != NULL) {
    if (var -> attrs & CVAR_ATTR_STRUCT_DECL) {
      if (scan_struct_members (var, s))
	return TRUE;
    }
  }
  
  for (var = global_cvars; var; var = var -> next) {
    if (!strcmp (var -> type, "struct") ||
 	!strcmp (var -> qualifier, "struct") ||
 	!strcmp (var -> type, "union") ||
 	!strcmp (var -> qualifier, "union"))
       if (scan_struct_members (var, s))
 	return TRUE;
   }

  for (var = typedefs; var; var = var -> next) {
    if (!strcmp (var -> type, "struct") ||
	!strcmp (var -> qualifier, "struct") ||
	!strcmp (var -> type, "union") ||
	!strcmp (var -> qualifier, "union"))
      if (scan_struct_members (var, s))
	return TRUE;
  }

  if (HAVE_FRAMES) {
    for (var = current_parser () -> cvars; var; var = var -> next) {
      if (!strcmp (var -> type, "struct") ||
	  !strcmp (var -> qualifier, "struct") ||
	  !strcmp (var -> type, "union") ||
	  !strcmp (var -> qualifier, "union"))
	if (scan_struct_members (var, s))
	  return TRUE;
    }
  }

  return FALSE;
}

static int scan_fn_cvar_params (CVAR *c, char *s) {
  CVAR *param;
  for (param = c -> params; param; param = param -> next) {
    if (param -> params) {
      if (scan_struct_members (param, s))
	return TRUE;
    }
    if (!strcmp (param -> name, s))
      return TRUE;
  }
  return FALSE;
}

int is_fn_param (char *s) {
  CVAR *var, *mbr;
  CFUNC *fn;

  _hash_all_initialize ();
  while ((fn = (CFUNC *)_hash_all (declared_functions)) != NULL) {
    for (mbr = fn -> params; mbr; mbr = mbr -> next) {
      if (!strcmp (mbr -> name, s))
	return TRUE;
    }
  }
  for (var = typedefs; var; var = var -> next) {
    if (VAR_IS_FN (var))
      if (scan_fn_cvar_params (var, s))
	return TRUE;
  }

  if (HAVE_FRAMES) {
    for (var = current_parser () -> cvars; var; var = var -> next) {
      if (VAR_IS_FN (var))
	if (scan_fn_cvar_params (var, s))
	  return TRUE;
    }
  }

  return FALSE;
}

/*
 *  Here because this check is only needed when registering
 *  variables.
 */

char *declaration_expr_from_cvar (CVAR *c, char *buf) {
  int n_derefs;
  if (*(c -> qualifier4)) {strcat (buf, c -> qualifier4); strcat (buf, " ");}
  if (*(c -> qualifier3)) {strcat (buf, c -> qualifier3); strcat (buf, " ");}
  if (*(c -> qualifier2)) {strcat (buf, c -> qualifier2); strcat (buf, " ");}
  if (*(c -> qualifier)) {strcat (buf, c -> qualifier); strcat (buf, " ");}
  if (*(c -> type)) {strcat (buf, c -> type); strcat (buf, " ");} 
  else {strcat (buf, "int"); strcat (buf, " ");}
  for (n_derefs = 0; n_derefs < c -> n_derefs; n_derefs++)
    strcat (buf, "*");
  if (*(c -> name)) {strcat (buf, c -> name);}
  return buf;
}

/*
 *  Should keep struct tags and declarations separately.
 */
CVAR *have_struct (char *struct_type) {
  CVAR *c;
  PARSER *p;
  int global_frame;
  p = current_parser ();

  for (c = global_cvars; c; c = c -> next) {
    if (!strcmp (c -> name, struct_type) ||
	!strcmp (c -> type, struct_type))
      return c;
  }

  if ((global_frame = is_global_frame ()) == FALSE) { /* Local frame */
    /*
     *  Add the variable to the parser's variable list.
     */
    for (c = p -> cvars; c; c = c -> next) {
      if (!strcmp (c -> name, struct_type) ||
	  !strcmp (c -> type, struct_type))
	return c;
    }
  }

  if ((c = get_typedef (struct_type)) != NULL)
    return c;

  return NULL;
}

CFUNC *cvar_to_cfunc (CVAR *c) {
  CFUNC *cfn;
  if ((cfn = (CFUNC *)calloc (1, sizeof (CFUNC))) == NULL) 
    _error ("add_function (): %s\n.", strerror (errno));
  if (*c -> decl) strcpy (cfn -> decl, c -> decl);
  if (*c -> type) strcpy (cfn -> return_type, c -> type);
  if (*c -> qualifier) strcpy (cfn -> qualifier_type, c -> qualifier);
  if (*c -> qualifier2) strcpy (cfn -> qualifier2_type, c -> qualifier2);
  if (*c -> storage_class) strcpy (cfn -> storage_class, c -> storage_class);
  if (*c -> name) strcpy (cfn -> decl, c -> name);
  cfn -> is_unsigned = c -> is_unsigned;
  cfn -> return_derefs = c -> n_derefs;
  cfn -> params = c -> params;
  cfn -> return_type_attrs = c -> type_attrs;
  return cfn;
}

void unlink_global_cvar (CVAR *c) {

  CVAR *var;
  int is_linked = FALSE;

  if (c == global_cvars) {
    global_cvars = c -> next;
    is_linked = TRUE;
  }

  for (var = global_cvars; var && !is_linked; var = var -> next) {
    if (var == c) is_linked = TRUE;
  }
  
  
  if (is_linked) {
    if (c -> next) c -> next -> prev = c -> prev;
    if (c -> prev) c -> prev -> next = c -> next;
  }
}

int is_c_type (char *s) {
#ifdef __GNUC__
  return is_c_data_type (s) ||
    get_typedef (s) ||
    get_type_conv_name (s) || 
    is_gnu_extension_keyword(s);
#else
  return is_c_data_type (s) ||
    get_type_conv_name (s) || 
    get_typedef (s);
#endif
}

int validate_type_declarations (CVAR *c) {
  int have_undefined_type = FALSE;
  if (c -> type[0] && !is_c_type (c -> type))
    have_undefined_type = TRUE;
  if (c -> qualifier[0] && !is_c_type (c -> qualifier))
    have_undefined_type = TRUE;
  if (c -> qualifier2[0] && !is_c_type (c -> qualifier2))
    have_undefined_type = TRUE;
  if (c -> qualifier3[0] && !is_c_type (c -> qualifier3))
    have_undefined_type = TRUE;
  if (c -> qualifier4[0] && !is_c_type (c -> qualifier4))
    have_undefined_type = TRUE;
  if (c -> storage_class[0] && !is_c_type (c -> storage_class))
    have_undefined_type = TRUE;

  /*
   *  Declaration rec should be correct enough by now.
   */
  if ((c -> attrs & CVAR_ATTR_STRUCT_DECL) ||
      (c -> attrs & CVAR_ATTR_STRUCT_PTR) ||
      (c -> attrs & CVAR_ATTR_STRUCT))
    return TRUE;

  if (have_undefined_type)
    return FALSE;
  else
    return TRUE;
}

CVAR *struct_member_from_expr (char *expr, CVAR *struct_defn) {
  int end_ptr, idx, n_th_label;
  CVAR *defn_mbr, *defn_mbr_mbr;

  end_ptr = tokenize (var_message_push, expr);
  defn_mbr = struct_defn;
  for (idx = N_MESSAGES, n_th_label = 0; idx >= end_ptr; idx--) {
    if (M_TOK(var_messages[idx]) == LABEL) {
      switch (++n_th_label)
	{
	case 0:
	case 1:
	  break;
	default:
	  for (defn_mbr_mbr = defn_mbr -> members; defn_mbr_mbr; 
	       defn_mbr_mbr = defn_mbr_mbr -> next) {
	    if (!strcmp (defn_mbr_mbr -> name, M_NAME(var_messages[idx]))) {
	      if (((defn_mbr_mbr -> type_attrs & CVAR_TYPE_STRUCT) ||
		  (defn_mbr_mbr -> type_attrs & CVAR_TYPE_UNION)) &&
		  (defn_mbr_mbr -> members == NULL)) {
		if (((defn_mbr = 
		      get_local_struct_defn (defn_mbr_mbr->type))==NULL)&&
		    ((defn_mbr = 
		      get_global_struct_defn (defn_mbr_mbr->type))==NULL)&&
		    ((defn_mbr = 
		      have_struct (defn_mbr_mbr->type))==NULL)){
		  _warning ("warning: struct_member_from_expr: could not find definition of struct expression \"%s\".\n", expr);
		  defn_mbr = defn_mbr_mbr;
		}
	      } else {
		defn_mbr = defn_mbr_mbr;
	      }
	    }
	  }
	  break;
	}
    }
  }
  DELETE_MESSAGES(var_messages,var_messageptr,N_MESSAGES)
  return defn_mbr;
}

