/* $Id: ccompat.c,v 1.2 2009/01/04 15:49:30 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.
*/

/*
 *   Compatibility functions for different C compilers.
 *   Revision history:
 *
 *   2006-02-15
 *     Compatibility with GCC on Linux and DJGPP on MS Windows.
 */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <dirent.h>
#include <ctype.h>
#include <errno.h>
#include "object.h"
#include "message.h"
#include "ctalk.h"

extern int verbose_opt;                /* Declared in main.c.               */
extern char *include_dirs[MAXUSERDIRS];
extern int n_include_dirs;
extern char input_source_file[FILENAME_MAX];
extern char *pkgname;
extern char *classlibdir;

static char gcc_target[MAXLABEL];       /* GNU C target and version.        */
static char gcc_version[MAXLABEL];

#ifdef __GNUC__
#ifdef __DJGPP__
#define GCC_LIBDIR "/djgpp/lib/gcc"    /* GNU C library directory.          */
#else
#define GCC_LIBDIR "/usr/lib/gcc-lib"
#endif
#endif

#ifdef __GNUC__                        /* GCC include directories.          */
char *cc_include_paths[] = {
  "/usr/local/include"                 /* Host-specific includes.           */
  "",                                  /* LIBDIR includes.                  */
  "",                                  /* /usr/TARGET/includes.             */
  "",                                  /* Standard includes.                */
  NULL
};
#define N_PATHS 4
#else                             /* Generic include directories.      */
char *cc_include_paths[] = {
  "/usr/local/include"       
  "/usr/include",
  NULL
};
#define N_PATHS 2
#endif

/* The compat include file's path must be relative to the include search
   directories. */
#ifdef __GNUC__
#define COMPAT_INCLUDE "gncompat.h"
#else
#define COMPAT_INCLUDE NULL       /* Only tested with GCC so far.     */
#endif

Boolean compat_include = False;   /* True if including compatibility
				     header files. */

/*
 *  After determining gcc_target, find the version subdirectory.
 *  Find the subdirectory with the major version that matches __GNUC__
 *  and the minor version (after the first decimal point) that
 *  matches __GNUC_MINOR__.
 *  If two subdirectories match, use the most recently installed 
 *  subdirectory.  TO DO - This needs to be developed and tested on a 
 *  system that has multiple GCC versions installed.
 */

char *verify_gnu_version (void) {

  DIR *dir;
  struct dirent *d;
  struct stat statbuf;
  char libtargetdir[FILENAME_MAX],  /* The target platform's lib directory. */
    s_major[MAXLABEL],              /* GCC major version.                   */
    s_minor[MAXLABEL],              /* GCC minor version.                   */
    *versiondirs[MAXARGS],          /* Matching version directories.        */
    *minor_dir_label;               /* Start of minor version in dir name.  */
  int n_dirs = 0; /*Avoid a warning*/ /* Number of matching version subdirs.*/

  SNPRINTF (s_major, MAXLABEL, "%d",   __GNUC__);
  SNPRINTF (s_minor, MAXLABEL, "%d", __GNUC_MINOR__);
  SNPRINTF (libtargetdir, (size_t)FILENAME_MAX, "%s/%s", GCC_LIBDIR, gcc_target);
  if ((dir = opendir (libtargetdir)) != NULL) {
    n_dirs = 0;
    while ((d = readdir (dir)) != NULL) {
      /* Match the major GCC version. */
      if (!strncmp (d -> d_name, s_major, strlen (s_major))) {
	/* Match the minor GCC version. */
	if ((minor_dir_label = index (d -> d_name, '.')) != NULL) {
	  ++minor_dir_label;
	  if (!strncmp (minor_dir_label, s_minor, strlen (s_minor)))
	    versiondirs[n_dirs++] = strdup (d -> d_name);
	}
      }
    }
    closedir (dir);
  }  else {
    if (verbose_opt)
      _warning ("Could not find a compiler target subdirectory in %s.\n",
		GCC_LIBDIR);
  }

  switch (n_dirs)
    {
    case 0:   /* No matches */
      if (verbose_opt)
	_warning ("Could not find a compiler version subdirectory in ");
      break;
    case 1:  /* Single match - verify the directory. */
      strcpy (gcc_version, versiondirs[0]);
      free (versiondirs[0]);
      SNPRINTF (libtargetdir, (size_t)FILENAME_MAX, "%s/%s/%s", GCC_LIBDIR, gcc_target, gcc_version);
      if (!stat (libtargetdir, &statbuf)) {
	if (S_ISDIR (statbuf.st_mode)) {
	  return gcc_version;
	} else {
	  _warning ("Error reading compiler library directory %s.\n", 
		    libtargetdir);
	  _warning ("See the -s command line option.\n");
	  exit (1);
	}
      }
      break;
      /* TO DO - Needs to be tested on a system that has multiple
	 GCC installations. */
    default:  /* Multiple matches - use the most recent directory. */
      break;
    }

  return NULL;

}

/*
 * Try to find the compiler target by looking for the subdirectory
 * under /usr/lib/gcc-lib.
 */

char *find_gcc_target_dir (void) {

#ifndef __DJGPP__
  struct dirent *d;
  DIR *dir;
#endif
  struct stat statbuf;
  char targetdir[FILENAME_MAX];

#ifdef __DJGPP__

  strcpy (gcc_target, "djgpp");

#else

  /* Try to find out what the compiler target is.  It may not 
     be the same as the autoconf $host.
  */
  if ((dir = opendir (GCC_LIBDIR)) != NULL) {
    *gcc_target = 0;
    while ((d = readdir (dir)) != NULL) {
      if (index (d -> d_name, '-'))
	strcpy (gcc_target, d -> d_name);
    }
    closedir (dir);
  }  else {
    _warning ("Could not find compiler target subdirectory in");
    _warning ("/usr/lib/gcc-lib. See the -s command line option.");
    exit (1);
  }

#endif
  SNPRINTF (targetdir, (size_t)FILENAME_MAX, "%s/%s", GCC_LIBDIR, gcc_target);
  if (!stat (targetdir, &statbuf))
    if (S_ISDIR (statbuf.st_mode))
      return gcc_target;

  _warning ("Could not find compiler target subdirectory");
  _warning ("%s. See the -s command line option.", targetdir);
  exit (1);

}

/* 
 *  For now, just elide the attributes.
 */

void gnu_attributes (MESSAGE_STACK messages, int msg_ptr) {

  int i,
    stack_end,
    attr_start,
    n_parens;

  stack_end = get_stack_top (messages);

  for (i = msg_ptr, n_parens = 0, attr_start = 0; i > stack_end; i--) {

    if (!strcmp (messages[i] -> name, "__attribute__"))
      attr_start = i;

    if (M_TOK(messages[i]) == SEMICOLON)
      break;

    if (attr_start) {

      if (messages[i] -> tokentype == OPENPAREN)
	++n_parens;

      if (messages[i] -> tokentype == CLOSEPAREN) {
	if (--n_parens == 0) {
	  strcpy (messages[i] -> name, " ");
	  messages[i] -> tokentype = WHITESPACE;
	  goto done;
	}
      }

      strcpy (messages[i] -> name, " ");
      messages[i] -> tokentype = WHITESPACE;
    }

  }

 done:
  return;
}

/*
 *   This function exists mainly to fill in GCC extensions that
 *   don't have normal typing; i.e., builtins like 
 *   pthread_descr_struct, etc.
 */
#ifdef __GNUC__
void gnuc_type_extensions (CVAR *c) {

  CVAR *t;
  
  if (!strcmp (c -> type, "_pthread_descr_struct") ||
      !strcmp (c -> qualifier, "_pthread_descr_struct") ||
      !strcmp (c -> name, "_pthread_descr")) {
    _new_cvar (CVARREF(t));
    strcpy (t -> type, "struct");
    strcpy (t -> name, "_pthread_descr_struct");
    add_variable_from_cvar (t);
  }
}
#endif

/*
 *  Later versions of the GNU C headers can cause the preprocessor
 *  to define out a tag, for example, 
 *  "extern void <tag-replaced-by-whitespace> __atribute ((NOTHROW));"
 *  This function elides such declarations so they don't cause compiler 
 *  warnings.
 */

#ifdef __GNUC__
void gnuc_fix_empty_extern (MESSAGE_STACK messages, int idx) {

  int i,
    stack_end, 
    decl_end_idx = -1; 
  Boolean have_tag = False,
    is_fn = False;
  MESSAGE *m;
  CFUNC *cfn;

  stack_end = get_stack_top (messages);

  for (i = idx; 
       (i > stack_end) && (decl_end_idx == -1);
       i--) {

    m = messages[i];

    if (M_ISSPACE (m)) 
      continue;

    if (M_TOK (m) == LABEL) {
      if (!is_c_keyword (M_NAME (m))) {
	if (global_var_is_declared (M_NAME(m)) ||
	    ((cfn = get_function (M_NAME(m))) != NULL)) {
	  have_tag = True;
	}
      }
    }

    if (M_TOK (m) == OPENBLOCK) {
      is_fn = True;
      decl_end_idx = i;
    }

    if (M_TOK (m) == SEMICOLON)
      decl_end_idx = i;
  }

  if (!have_tag && !is_fn) {
    for (i = idx; i > decl_end_idx; i--) {
      m = messages[i];
      strcpy (m -> name, " ");
      m ->  tokentype = WHITESPACE;
    }
  }

}
#endif

void print_libdirs (void) {
  printf ("%s\n", classlibdir);
}
