/*  This file is part of the program psim.

    Copyright (C) 1994-1995,1997 Andrew Cagney <cagney@highland.com.au>

    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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
 
    */


#include <sys/types.h>
#include <sys/stat.h>
#include <stdio.h>
#include <fcntl.h>
#include <ctype.h>

#include "config.h"
#include "misc.h"
#include "lf.h"
#include "table.h"

#ifdef HAVE_UNISTD_H
#include <unistd.h>
#endif

#ifdef HAVE_STDLIB_H
#include <stdlib.h>
#endif

typedef struct _open_table open_table;
struct _open_table {
  size_t size;
  char *buffer;
  char *pos;
  line_ref pseudo_line;
  line_ref real_line;
  open_table *parent;
  table *root;
};
struct _table {
  open_table *current;
};


static line_ref *
current_line (open_table *file)
{
  line_ref *entry = ZALLOC (line_ref);
  *entry = file->pseudo_line;
  return entry;
}

static table_entry *
new_table_entry (open_table *file,
		 table_entry_type type)
{
  table_entry *entry;
  entry = ZALLOC (table_entry);
  entry->file = file->root;
  entry->line = current_line (file);
  entry->type = type;
  return entry;
}

static void
set_nr_table_entry_fields (table_entry *entry,
			   int nr_fields)
{
  entry->field = NZALLOC (char*, nr_fields + 1);
  entry->nr_fields = nr_fields;
}


void
table_push (table *root,
	    line_ref *line,
	    table_include *includes,
	    const char *file_name)
{
  int fd;
  struct stat stat_buf;
  open_table *file;
  table_include dummy;
  table_include *include = &dummy;

  /* dummy up a search of this directory */
  dummy.next = includes;
  dummy.dir = "";

  /* create a file descriptor */
  file = ZALLOC (open_table);
  if (file == NULL)
    {
      perror (file_name);
      exit (1);
    }
  file->root = root;
  file->parent = root->current;
  root->current = file;

  while (1)
    {
      /* save the file name */
      char *dup_name = NZALLOC (char, strlen (include->dir) + strlen (file_name) + 2);
      if (dup_name == NULL)
	{
	  perror (file_name);
	  exit (1);
	}
      if (include->dir[0] != '\0')
	{
	  strcat (dup_name, include->dir);
	  strcat (dup_name, "/");
	}
      strcat (dup_name, file_name);
      file->real_line.file_name = dup_name;
      file->pseudo_line.file_name = dup_name;
      /* open the file */
      fd = open (dup_name, O_RDONLY, 0);
      if (fd >= 0)
	break;
      /* zfree (dup_name); */
      if (include->next == NULL)
	{
	  if (line != NULL)
	    error (line, "Problem opening file `%s'\n", file_name);
	  perror (file_name);
	  exit (1);
	}
      include = include->next;
  }


  /* determine the size */
  if (fstat (fd, &stat_buf) < 0) {
    perror (file_name);
    exit (1);
  }
  file->size = stat_buf.st_size;

  /* allocate this much memory */
  file->buffer = (char*) zalloc (file->size + 1);
  if (file->buffer == NULL)
    {
      perror (file_name);
      exit (1);
    }
  file->pos = file->buffer;

  /* read it all in */
  if (read (fd, file->buffer, file->size) < file->size) {
    perror (file_name);
    exit (1);
  }
  file->buffer[file->size] = '\0';

  /* set the initial line numbering */
  file->real_line.line_nr = 1; /* specifies current line */
  file->pseudo_line.line_nr = 1; /* specifies current line */

  /* done */
  close (fd);
}

table *
table_open (const char *file_name)
{
  table *root;

  /* create a file descriptor */
  root = ZALLOC (table);
  if (root == NULL)
    {
      perror (file_name);
      exit (1);
    }

  table_push (root, NULL, NULL, file_name);
  return root;
}

char *
skip_spaces (char *chp)
{
  while (1)
    {
      if (*chp == '\0'
	  || *chp == '\n'
	  || !isspace (*chp))
	return chp;
      chp++;
    }
}


char *
back_spaces (char *start, char *chp)
{
  while (1)
    {
      if (chp <= start
	  || !isspace (chp[-1]))
	return chp;
      chp--;
    }
}

char *
skip_digits (char *chp)
{
  while (1)
    {
      if (*chp == '\0'
	  || *chp == '\n'
	  || !isdigit (*chp))
	return chp;
      chp++;
    }
}

char *
skip_to_separator (char *chp,
		   char *separators)
{
  while (1)
    {
      char *sep = separators;
      while (1)
	{
	  if (*chp == *sep)
	    return chp;
	  if (*sep == '\0')
	    break;
	  sep++;
	}
      chp++;
    }
}

static char *
skip_to_null (char *chp)
{
  return skip_to_separator (chp, "");
}


static char *
skip_to_nl (char * chp)
{
  return skip_to_separator (chp, "\n");
}


static void
next_line (open_table *file)
{
  file->pos = skip_to_nl (file->pos);
  if (*file->pos == '0')
    error (&file->pseudo_line, "Missing <nl> at end of line\n");
  *file->pos = '\0';
  file->pos += 1;
  file->real_line.line_nr += 1;
  file->pseudo_line.line_nr += 1;
}


extern table_entry *
table_read (table *root)
{
  open_table *file = root->current;
  table_entry *entry = NULL;
  while(1)
    {

      /* end-of-file? */
      while (*file->pos == '\0')
	{
	  if (file->parent != NULL)
	    {
	      file = file->parent;
	      root->current = file;
	    }
	  else
	    return NULL;
	}

      /* code_block? */
      if (*file->pos == '{')
	{
	  char *chp;
	  next_line (file); /* discard leading brace */
	  entry = new_table_entry (file, table_code_entry);
	  chp = file->pos;
	  /* determine how many lines are involved - look for <nl> "}" */
	  {
	    int nr_lines = 0;
	    while (*file->pos != '}')
	      {
		next_line (file);
		nr_lines++;
	      }
	    set_nr_table_entry_fields (entry, nr_lines);
	  }
	  /* now enter each line */
	  {
	    int line_nr;
	    for (line_nr = 0; line_nr < entry->nr_fields; line_nr++)
	      {
		if (strncmp (chp, "  ", 2) == 0)
		  entry->field[line_nr] = chp + 2;
		else
		  entry->field[line_nr] = chp;
		chp = skip_to_null (chp) + 1;
	      }
	    /* skip trailing brace */
	    ASSERT (*file->pos == '}');
	    next_line (file);
	  }
	  break;
	}

      /* tab block? */
      if (*file->pos == '\t')
	{
	  char *chp = file->pos;
	  entry = new_table_entry (file, table_code_entry);
	  /* determine how many lines are involved - look for <nl> !<tab> */
	  {
	    int nr_lines = 0;
	    int nr_blank_lines = 0;
	    while (1)
	      {
		if (*file->pos == '\t')
		  {
		    nr_lines = nr_lines + nr_blank_lines + 1;
		    nr_blank_lines = 0;
		    next_line (file);
		  }
		else
		  {
		    file->pos = skip_spaces (file->pos);
		    if (*file->pos != '\n')
		      break;
		    nr_blank_lines++;
		    next_line (file);
		  }
	      }
	    set_nr_table_entry_fields (entry, nr_lines);
	  }
	  /* now enter each line */
	  {
	    int line_nr;
	    for (line_nr = 0; line_nr < entry->nr_fields; line_nr++)
	      {
		if (*chp == '\t')
		  entry->field[line_nr] = chp + 1;
		else
		  entry->field[line_nr] = ""; /* blank */
		chp = skip_to_null (chp) + 1;
	      }
	  }
	  break;
	}

      /* cpp directive? */
      if (file->pos[0] == '#')
	{
	  char *chp = skip_spaces (file->pos + 1);

	  /* cpp line-nr directive - # <line-nr> "<file>" */
	  if (isdigit (*chp)
	      && *skip_digits (chp) == ' '
	      && *skip_spaces (skip_digits (chp)) == '"')
	    {
	      int line_nr;
	      char *file_name;
	      file->pos = chp;
	      /* parse the number */
	      line_nr = atoi(file->pos) - 1;
	      /* skip to the file name */
	      while (file->pos[0] != '0'
		     && file->pos[0] != '"'
		     && file->pos[0] != '\0')
		file->pos++;
	      if (file->pos[0] != '"')
		error (&file->real_line, "Missing opening quote in cpp directive\n");
	      /* parse the file name */
	      file->pos++;
	      file_name = file->pos;
	      while (file->pos[0] != '"'
		     && file->pos[0] != '\0')
		file->pos++;
	      if (file->pos[0] != '"')
		error (&file->real_line, "Missing closing quote in cpp directive\n");
	      file->pos[0] = '\0';
	      file->pos++;
	      file->pos = skip_to_nl (file->pos);
	      if (file->pos[0] != '\n')
		error (&file->real_line, "Missing newline in cpp directive\n");
	      file->pseudo_line.file_name = file_name;
	      file->pseudo_line.line_nr = line_nr;
	      next_line (file);
	      continue;
	    }

	  /* #define and #undef - not implemented yet */

	  /* Old style # comment */
	  next_line (file);
	  continue;
	}

      /* blank line or end-of-file? */
      file->pos = skip_spaces (file->pos);
      if (*file->pos == '\0')
	error (&file->pseudo_line, "Missing <nl> at end of file\n");
      if (*file->pos == '\n')
	{
	  next_line (file);
	  continue;
	}

      /* comment - leading // or # - skip */
      if ((file->pos[0] == '/' && file->pos[1] == '/')
	  || (file->pos[0] == '#'))
	{
	  next_line (file);
	  continue;
	}

      /* colon field */
      {
	char *chp = file->pos;
	entry = new_table_entry (file, table_colon_entry);
	next_line (file);
	/* figure out how many fields */
	{
	  int nr_fields = 1;
	  char *tmpch = chp;
	  while (1)
	    {
	      tmpch = skip_to_separator (tmpch, "\\:");
	      if (*tmpch == '\\')
		{
		  /* eat the escaped character */
		  char *cp = tmpch;
		  while (cp[1] != '\0')
		    {
		      cp[0] = cp[1];
		      cp++;
		    }
		  cp[0] = '\0';
		  tmpch++;
		}
	      else if (*tmpch != ':')
		break;
	      else
		{
		  *tmpch = '\0';
		  tmpch++;
		  nr_fields++;
		}
	    }
	  set_nr_table_entry_fields (entry, nr_fields);
	}
	/* now parse them */
	{
	  int field_nr;
	  for (field_nr = 0; field_nr < entry->nr_fields; field_nr++)
	    {
	      chp = skip_spaces (chp);
	      entry->field[field_nr] = chp;
	      chp = skip_to_null (chp);
	      *back_spaces (entry->field[field_nr], chp) = '\0';
	      chp++;
	    }
	}
	break;
      }

    }

  ASSERT (entry == NULL || entry->field[entry->nr_fields] == NULL);
  return entry;
}

extern void
table_print_code (lf *file,
		  table_entry *entry)
{
  int field_nr;
  int nr = 0;
  for (field_nr = 0;
       field_nr < entry->nr_fields;
       field_nr++)
    {
      char *chp = entry->field[field_nr];
      int in_bit_field = 0;
      if (*chp == '#')
	lf_indent_suppress(file);
      while (*chp != '\0') 
	{
	  if (chp[0] == '{'
	      && !isspace(chp[1])
	      && chp[1] != '\0')
	    {
	      in_bit_field = 1;
	      nr += lf_putchr(file, '_');
	    }
	  else if (in_bit_field && chp[0] == ':')
	    {
	      nr += lf_putchr(file, '_');
	    }
	  else if (in_bit_field && *chp == '}')
	    {
	      nr += lf_putchr(file, '_');
	      in_bit_field = 0;
	    }
	  else 
	    {
	      nr += lf_putchr(file, *chp);
	    }
	  chp++;
	}
      if (in_bit_field)
	{
	  line_ref line = *entry->line;
	  line.line_nr += field_nr;
	  error (&line, "Bit field brace miss match\n");
	}
      nr += lf_putchr(file, '\n');
    }
}



void
dump_line_ref (lf *file,
		 char *prefix,
		 const line_ref *line,
		 char *suffix)
{
  lf_printf (file, "%s(line_ref*) 0x%lx", prefix, (long) line);
  if (line != NULL)
    {
      lf_indent (file, +1);
      lf_printf (file, "\n(line_nr %d)", line->line_nr);
      lf_printf (file, "\n(file_name %s)", line->file_name);
      lf_indent (file, -1);
    }
  lf_printf (file, "%s", suffix);
}


static const char *
table_entry_type_to_str (table_entry_type type)
{
  switch (type)
    {
    case table_code_entry: return "code-entry";
    case table_colon_entry: return "colon-entry";
    }
  return "*invalid*";
}

void
dump_table_entry(lf *file,
		 char *prefix,
		 const table_entry *entry,
		 char *suffix)
{
  lf_printf (file, "%s(table_entry*) 0x%lx", prefix, (long) entry);
  if (entry != NULL)
    {
      int field;
      lf_indent (file, +1);
      dump_line_ref (file, "\n(line ", entry->line, ")");
      lf_printf (file, "\n(type %s)", table_entry_type_to_str (entry->type));
      lf_printf (file, "\n(nr_fields %d)", entry->nr_fields);
      lf_printf (file, "\n(fields");
      lf_indent (file, +1);
      for (field = 0; field < entry->nr_fields; field++)
	lf_printf (file, "\n\"%s\"", entry->field[field]);
      lf_indent (file, -1);
      lf_printf (file, ")");
      lf_indent (file, -1);
    }
  lf_printf (file, "%s", suffix);
}


#ifdef MAIN
int
main(int argc, char **argv)
{
  table *t;
  table_entry *entry;
  lf *l;
  int line_nr;

  if (argc != 2)
    {
      printf("Usage: table <file>\n");
      exit (1);
    }

  t = table_open (argv[1]);
  l = lf_open ("-", "stdout", lf_omit_references, lf_is_text, "tmp-table");

  line_nr = 0;
  do
    {
      char line[10];
      entry = table_read (t);
      line_nr ++;
      sprintf (line, "(%d ", line_nr);
      dump_table_entry (l, line, entry, ")\n");
    }
  while (entry != NULL);

  return 0;
}
#endif
