#pragma ident "etags_out.c 16.9 96/04/19"

/* FILE NOT I18N */

/* Tags file maker to go with GNUmacs
   Copyright (C) 1984, 1987, 1988 Free Software Foundation, Inc. and
   Copyright (C) %H Sun Microsystems.

   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., 675 Mass Ave, Cambridge, MA 02139, USA.
   Or contact Sam Kendall, CenterLine Software Inc., 10 Fawcett Street,
   Cambridge, MA 02138  USA.  Email is kendall@CenterLine.COM or
   uunet!saber!kendall.
*/

/* Output format appropriate for sbenter */
/* Currently still into a TAGS file */
/* Add sccs id information   */

#include <stdio.h>
#include <locale.h>

#include <unistd.h>
#include <string.h>
#include "etags.h"

#define streq(s1, s2)   (strcmp(s1, s2) == 0)

static int language = LANG_NONE;
static int cur_language_run = LANG_NONE;

/*
 * Open a pipe into a subprocess
 */

#define SBENTER_PROG "sbenter"

/* the next one is useful for debugging */

#define SBENTER_DEBUG_PROG "echo"

FILE *
sb_open_pipe ()
{
  FILE *pipe;
  char *command = NULL;
  
/* self-find is needed if we want to have sbenter not visible */

  if (base_progname == NULL)
    command = savestr (SBENTER_PROG);
  else
    command = concat (base_progname, "/", SBENTER_PROG);

  if ((pipe = popen(command, "w")) == NULL) {
    fatal ("failed to start `%s'", command);
  }
  return (pipe);
}


/*
 * Collect hash and char count values for each line.
 * The hash value is emited at the end of each file,
 * or whenever we run out of space in our array.
 */

#define TABLE_SIZE 100

int table_cnt = 0;		/* number of entries in table */
int first_lineno = 1;		/* lineno's start at 1 */

typedef struct {
  int count;
  unsigned int hash;
} Line_id_table;

Line_id_table table[TABLE_SIZE];

void
sb_print_line_id ()
{
  register int i;

  if (table_cnt <= 0)
    return;

  fprintf (outf, "H %d", table_cnt);
  for (i=0; i<table_cnt; i++)
    fprintf (outf, " %d %d", table[i].count, table[i].hash);
  fprintf (outf, "\n");

  first_lineno += table_cnt;
  for (i=0; i<table_cnt; i++)
    table[i].count = table[i].hash = 0;
  table_cnt = 0;
}

void
sb_enter_line_id(count, hash)
     int count;
     unsigned int hash;
{
  int entry;

  entry = lineno - first_lineno;
  if (entry >= table_cnt)	/* i.e. not printed and not seen */
    if (entry != table_cnt)
      fatal ("internal error: skipped line. Lineno: %d", lineno);
    else
      {
	table[entry].count = count;
	table[entry].hash = hash;
	table_cnt += 1;
	if (table_cnt == TABLE_SIZE)
	  sb_print_line_id();
      }
}


sb_print_c_entry(node)
     reg NODE *node;
{
  if (node->is_func) {
    fprintf (outf, "F Function %s %d %d\n",
	     node->name, node->lno,
	     (node->next == NULL)?lineno:node->next->lno-1);
    fprintf (outf, "S %s %d %s\n",
	     node->name, node->lno, "cb_c_def_global_func_w_body");
  }
  if (node->is_call && (node->caller != NULL)) {
    fprintf (outf, "S %s %d %s\n",
	     node->name, node->lno, "cb_c_ref_func_call");
    fprintf (outf, "A Function %s Call Function %s %d\n",
	     node->caller->name, node->name, node->lno);
  }
}

/* Determine whether IN is a member function, updating
 * IS_MEMBER with the proper value.  If it is a member,
 * CONVERTED is an encoding (for SB) of the name, while
 * FUNCTION is the unencoded name (without the XX::)
 */

#define MAXIDLEN 2048

static
sb_converted_name(in, is_member, converted, function)
     char *in;
     int *is_member;
     char *converted;
     char *function;
{
  static char class[MAXIDLEN];
  static char func[MAXIDLEN];
  static char ret[MAXIDLEN];

  char *pos;
  int index;
  int len = strlen(in);

  if ((in == NULL) || (*in == '\0')) {
    fatal ("null string passed to sb_converted_name");
  }
  if (len >= MAXIDLEN)
    fatal ("too long an identifier");

  if ((pos = strchr(in, ':')) == NULL) {
    *is_member = 0;
    (void) strcpy(converted, in);
    (void) strcpy(function, in);
    return;
  }
  
  index = id_len(in, 1);

  (void) strncpy(class, in, index);
  class[index] = '\0';

  index = id_len(in+len-1, -1);
  (void) strncpy(func, in+len-index, index);
  func[len-(index+1)] = '\0';

  if ((int) strlen(class) + (int) strlen(func) + 9 >= MAXIDLEN)
    fatal("too long a converted identifier");

  sprintf(ret, "#c%s::#n%s()", class, func);
  *is_member = 1;
  (void) strcpy(converted, ret);
  (void) strcpy(function, func);
}

static
sb_print_c_plus_plus_entry(node)
     reg NODE *node;
{
  static char encoded_node_name[MAXIDLEN];
  static char short_node_name[MAXIDLEN];
  int is_node_member_function;

  sb_converted_name(node->name,
		    &is_node_member_function,
		    encoded_node_name,
		    short_node_name
		    );

  if (node->is_func) {
    fprintf (outf, "F Function %s %d %d\n",
	     encoded_node_name, node->lno,
	     (node->next == NULL)?lineno:node->next->lno-1);

    fprintf (outf, "S %s %d %s\n",
	     short_node_name, node->lno,
	     is_node_member_function
	      ? "cb_sun_c_plus_plus_def_member_global_func_w_body"
	      : "cb_c_def_global_func_w_body");
  }

  if (node->is_call && (node->caller != NULL)) {
    static char encoded_caller_name[MAXIDLEN];
    static char short_caller_name[MAXIDLEN];
    int is_caller_member_function;

    fprintf (outf, "S %s %d %s\n",
	     short_node_name, node->lno, "cb_c_ref_func_call");

    sb_converted_name(node->caller->name,
		      &is_caller_member_function,
		      encoded_caller_name,
		      short_caller_name);
    
    if (encoded_caller_name[0] == '\0')
      fatal ("null encoded caller name!");

    fprintf (outf, "A Function %s Call Function %s %d\n",
	     encoded_caller_name, encoded_node_name, node->lno);
  }
}

static
sb_print_for_pas_entry(node)
     reg NODE *node;
{
  /* Semantic tags are probably wrong as they are copied from C routine */

  if (node->is_func) {
    fprintf (outf, "F Function %s %d %d\n",
	     node->name, node->lno,
	     (node->next == NULL)?lineno:node->next->lno-1);
    fprintf (outf, "S %s %d %s\n",
	     node->name, node->lno, "cb_c_def_global_func_w_body");
  }
  if (node->is_call && (node->caller != NULL)) {
    fprintf (outf, "S %s %d %s\n",
	     node->name, node->lno, "cb_c_ref_func_call");
    fprintf (outf, "A Function %s Call Function %s %d\n",
	     node->caller->name, node->name, node->lno);
  }
}

static (*sb_current_print_entry)(NODE *);	/* global to indirect through */

/*
 * Valid arguments are between LANG_FIRST and LANG_LAST
 */

static char *filename;		/* name of current file */

void
note_language (lang, file)
     int lang;
     char * file;
{
  if ((lang <= LANG_FIRST) || (lang >= LANG_LAST))
    fatal ("internal error in note_language");
  language = lang;

  filename = savestr (file);
  first_lineno = 1;

  if (sflag) {
    int new_run = 1;
  
    if (cur_language_run == LANG_NONE)
      cur_language_run = language;
    else if (language != cur_language_run) {
      if (outfflag) {
	fprintf (outf, "\n");
	cur_language_run = language;
      }
      else
	{
	  pclose (outf);
	  outf = sb_open_pipe ();
	  cur_language_run = language;
	}
    } else
      new_run = 0;

    if (new_run)
      switch (language)
	{
	case LANG_TEX:
	  fatal ("TeX is not supported in -s mode");
	case LANG_SCHEME:
	case LANG_LISP:
	case LANG_AS:
	case LANG_C:
	  fprintf (outf, "L ansi_c\n");
	  sb_current_print_entry = &sb_print_c_entry;
	  break;
	case LANG_C_PLPL:
	case LANG_C_STAR:
	  fprintf (outf, "L sun_c_plus_plus\n");
	  sb_current_print_entry = &sb_print_c_plus_plus_entry;
	  break;
	case LANG_FORTRAN:
	case LANG_PASCAL:
	  fprintf (outf, "L ansi_c\n"); /* this is wrong */
	  sb_current_print_entry = &sb_print_for_pas_entry;
	  break;
	default:
	  fatal ("internal error in note_language");
	}
    fprintf (outf, "P %s\n", file);
  }
}



void
put_entries (node)
     reg NODE *node;
{
  reg char *sp;

  if (node == NULL)
    return;

  /* Output subentries that precede this one */
  put_entries (node->left);

  /* Output this entry */

  if (sflag)
    (*sb_current_print_entry)(node);
  else if (eflag)
    {
      if (node->rewritten)
	{
	  fprintf (outf, "%s\177%d,%d,\001%s\n",
		   node->pat, node->lno, node->cno, node->name);
	}
      else
	{
	  fprintf (outf, "%s\177%d,%d\n",
		   node->pat, node->lno, node->cno);
	}
    }
  else if (!xflag)
    {
      fprintf (outf, "%s\t%s\t",
	       node->name, node->file);

      if (node->is_func)
	{		/* a function */
	  putc (searchar, outf);
	  putc ('^', outf);

	  for (sp = node->pat; *sp; sp++)
	    {
	      if (*sp == '\\' || *sp == searchar)
		putc ('\\', outf);
	      putc (*sp, outf);
	    }
	  putc (searchar, outf);
	}
      else
	{		/* a typedef; text pattern inadequate */
	  fprintf (outf, "%d", node->lno);
	}
      putc ('\n', outf);
    }
  else if (vflag)
    fprintf (stdout, "%s %s %d\n",
	     node->name, node->file, (node->lno+63)/64);
  else
    fprintf (stdout, "%-16s%4d %-16s %s\n",
	     node->name, node->lno, node->file, node->pat);

  /* Output subentries that follow this one */
  put_entries (node->right);
}

/* Length of a number's decimal representation. */
int
number_len (num)
     long num;
{
  int len = 0;
  if (!num)
    return 1;
  for (; num; num /= 10)
    ++len;
  return len;
}
  
/*
 * Return total number of characters that put_entries will output for
 * the nodes in the subtree of the specified node.  Works only if eflag
 * is set, but called only in that case.  This count is irrelevant with
 * the new tags.el, but is still supplied for backward compatibility.
 *
 */
int
total_size_of_entries (node)
     reg NODE *node;
{
  reg int total;

  if (node == NULL)
    return 0;

  total = 0;
  for ( ; node; node = node->right)
    {
      /* Count left subentries. */
      total += total_size_of_entries (node->left);

      /* Count this entry */
      total += strlen (node->pat) + 1;
      total += number_len ((long) node->lno) + 1 + number_len (node->cno) + 1;
      if (node->rewritten)
	total += 2 + strlen (node->name); /* ,\001name */
    }

  return total;
}

/*
 * This routine gets called on each file argument
 */

void
process_file (file)
     char *file;
{
  if (streq (file, outfile) && ! streq (outfile, "-"))
    {
      fprintf (stderr, "Skipping inclusion of %s in self.\n", file);
      return;
    }
  if (iflag)
    {
      fprintf (outf, "\f\n%s,include\n", file);
      return;
    }
  if (sflag)
    {
      /* NULL */
    }
  else if (eflag)
    {
      char *cp = strrchr (file, '/');
      if (cp)  ++cp;  else  cp = file;
      if (streq (cp, outfile)) /*file == "TAGS"*/
	{
	  fprintf (outf, "\f\n%s,include\n", file);
	  return;
	}
    }
  if( find_entries (file))
    if (sflag)
      {
        put_entries (head);
        sb_print_line_id ();
        fprintf (outf, "F Source_File %s 1 %d\n",
	         filename, lineno);
#if 0
        fprintf (outf, "F Directory %s 1 %d\n",
	         YYY, lineno);
#endif
        fprintf (outf, "Z\n");
        free_tree (head);
        head = NULL;
      }
    else if (eflag)
      {
        fprintf (outf, "\f\n%s,%d\n",
	         file, total_size_of_entries (head));
        put_entries (head);
        free_tree (head);
        head = NULL;
      }
}
