/* GNU gettext - internationalization aids
   Copyright (C) 1995 Free Software Foundation, Inc.

   This file was written by Peter Miller <pmiller@agso.gov.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, 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.  */


#ifdef HAVE_CONFIG_H
# include "config.h"
#endif

#include <ctype.h>
#include <errno.h>
#include <stdio.h>
#include <sys/types.h>

#include <libintl.h>
#define _(str) gettext(str)

#if HAVE_VPRINTF || HAVE_DOPRNT
# if __STDC__
#  include <stdarg.h>
#  define VA_START(args, lastarg) va_start(args, lastarg)
# else
#  include <varargs.h>
#  define VA_START(args, lastarg) va_start(args)
# endif
#else
# define va_alist a1, a2, a3, a4, a5, a6, a7, a8
# define va_dcl char *a1, *a2, *a3, *a4, *a5, *a6, *a7, *a8;
#endif

#include "po-lex.h"
#include "po-gram.h"
#include "system.h"
#include "error.h"
#include "po-gram_gen.h"


static FILE *fp;
lex_pos_ty gram_pos;
int gram_nerrors;
int gram_max_allowed_errors = 20;
static int pass_comments;


/* Prototypes for local functions.  */
static int lex_getc __P ((void));
static void lex_ungetc __P ((int __ch));
static int keyword_p __P ((char *__s));
static int control_sequence __P ((void));


void
lex_open (fname)
     const char *fname;
{
  fp = open_po_file (fname, &gram_pos.file_name);
  if (!fp)
    error (EXIT_FAILURE, errno,
	   _("error while opening \"%s\" for reading"), fname);

  gram_pos.line_number = 1;
  gram_nerrors = 0;
  pass_comments = 0;
}


void
lex_close ()
{
  if (gram_nerrors)
    error (EXIT_FAILURE, 0, _("%s: found %d fatal errors"),
	   gram_pos.file_name, gram_nerrors);

  if (fp != stdin)
    fclose (fp);
  fp = NULL;
  gram_pos.file_name = 0;
  gram_pos.line_number = 0;
  gram_nerrors = 0;
}


#if !__STDC__ || !defined __GNUC__ || __GNUC__ == 1
/* VARARGS1 */
void
# if defined VA_START && __STDC__
po_gram_error (const char *fmt, ...)
# else
po_gram_error (fmt, va_alist)
     const char *fmt;
     va_dcl
# endif
{
# ifdef VA_START
  va_list ap;
  char *buffer;

  VA_START (ap, fmt);

  vasprintf (&buffer, fmt, ap);
  va_end (ap);
  error_with_loc (0, 0, gram_pos.file_name, gram_pos.line_number,
		  "%s", buffer);
# else
  error_with_loc (0, 0, gram_pos.file_name, gram_pos.line_number, fmt,
	 a1, a2, a3, a4, a5, a6, a7, a8);
# endif

  if (++gram_nerrors >= gram_max_allowed_errors)
    error (1, 0, _("%s: too many errors, aborting"), gram_pos.file_name);
}


/* VARARGS2 */
void
# if defined VA_START && __STDC__
gram_error_with_loc (const lex_pos_ty *pp, const char *fmt, ...)
# else
gram_error_with_loc (pp, fmt, va_alist)
     const lex_pos_ty *pp;
     const char *fmt;
     va_dcl
# endif
{
# ifdef VA_START
  va_list ap;
  char *buffer;

  VA_START (ap, fmt);

  vasprintf (&buffer, fmt, ap);
  va_end (ap);
  error_with_loc (0, 0, pp->file_name, pp->line_number, "%s", buffer);
# else
  error_with_loc (0, 0, pp->file_name, pp->line_number, fmt,
	 a1, a2, a3, a4, a5, a6, a7, a8);
# endif

  if (*fmt != '.' && ++gram_nerrors >= gram_max_allowed_errors)
    error (1, 0, _("%s: too many errors, aborting"), pp->file_name);
}
#endif


static int
lex_getc ()
{
  int c;

  for (;;)
    {
      c = getc (fp);
      switch (c)
	{
	case EOF:
	  if (ferror (fp))
	    error (EXIT_FAILURE, errno,	_("error while reading \"%s\""),
		   gram_pos.file_name);
	  return EOF;

	case '\n':
	  ++gram_pos.line_number;
	  return '\n';

	case '\\':
	  c = getc (fp);
	  if (c != '\n')
	    {
	      if (c != EOF)
		ungetc (c, fp);
	      return '\\';
	    }
	  ++gram_pos.line_number;
	  break;

	default:
	  return c;
	}
    }
}


static void
lex_ungetc (c)
     int c;
{
  switch (c)
    {
    case EOF:
      break;

    case '\n':
      --gram_pos.line_number;
      /* FALLTHROUGH */

    default:
      ungetc (c, fp);
      break;
    }
}


static int
keyword_p (s)
     char *s;
{
  if (!strcmp (s, "domain"))
    return DOMAIN;
  if (!strcmp (s, "msgid"))
    return MSGID;
  if (!strcmp (s, "msgstr"))
    return MSGSTR;
  po_gram_error (_("keyword \"%s\" unknown"), s);
  return NAME;
}


static int
control_sequence ()
{
  int c;
  int val;
  int max;

  c = lex_getc ();
  switch (c)
    {
    case 'n':
      return '\n';

    case 't':
      return '\t';

    case 'b':
      return '\b';

    case 'r':
      return '\r';

    case 'f':
      return '\f';

    case '\\':
    case '"':
      return c;

    case '0': case '1': case '2': case '3':
    case '4': case '5': case '6': case '7':
      val = 0;
      for (max = 0; max < 3; ++max)
	{
	  /* Warning: not portable, can't depend on '0'..'7' ordering.  */
	  val = val * 8 + c - '0';
	  c = lex_getc ();
	  switch (c)
	    {
	    case '0': case '1': case '2': case '3':
	    case '4': case '5': case '6': case '7':
	      continue;

	    default:
	      break;
	    }
	  break;
	}
      lex_ungetc (c);
      return val;

    case 'x': case 'X':
      c = lex_getc ();
      if (c == EOF || !isxdigit (c))
	break;

      val = 0;
      for (;;)
	{
	  val *= 16;
	  if (isdigit (c))
	    /* Warning: not portable, can't depend on '0'..'9' ordering */
	    val += c - '0';
	  else if (isupper (c))
	    /* Warning: not portable, can't depend on 'A'..'F' ordering */
	    val += c - 'A' + 10;
	  else
	    /* Warning: not portable, can't depend on 'a'..'f' ordering */
	    val += c - 'a' + 10;

	  c = lex_getc ();
	  switch (c)
	    {
	    case '0': case '1': case '2': case '3': case '4':
	    case '5': case '6': case '7': case '8': case '9':
	    case 'a': case 'b': case 'c': case 'd': case 'e': case 'f':
	    case 'A': case 'B': case 'C': case 'D': case 'E': case 'F':
	      continue;

	    default:
	      break;
	    }
	  break;
	}
      return val;
    }
  po_gram_error (_("illegal control sequence"));
  return ' ';
}


int
po_gram_lex ()
{
  static char *buf;
  static size_t bufmax;
  int c;
  size_t bufpos;

  for (;;)
    {
      c = lex_getc ();
      switch (c)
	{
	case EOF:
	  /* Yacc want this for end of file.  */
	  return 0;

	case ' ':
	case '\t':
	case '\n':
	case '\r':
	case '\f':
	  break;

	case '#':
	  /* Accumulate comments into a buffer.  If we have been asked
 	     to pass comments, generate a COMMENT token, otherwise
 	     discard it.  */
 	  bufpos = 0;
	  for (;;)
	    {
	      c = lex_getc ();
	      if (c == EOF || c == '\n')
		break;
	      if (bufpos >= bufmax)
	        {
	          bufmax += 100;
	          buf = xrealloc (buf, bufmax);
	        }
	      buf[bufpos++] = c;
	    }
	  if (bufpos >= bufmax)
	    {
	      bufmax += 100;
	      buf = xrealloc (buf, bufmax);
	    }
	  buf[bufpos] = 0;

	  /* Do not pass the special #! comments inserted by msgmerge,
	     otherwise they will multiply each time the files pass
	     through msgmerge.  */
	  if (pass_comments && buf[0] != '!')
	    {
	      po_gram_lval.string = buf;
	      return COMMENT;
	    }
	  break;

	case '"':
	  bufpos = 0;
	  for (;;)
	    {
	      c = lex_getc ();
	      if (c == '\n')
		{
		  po_gram_error (_("end-of-line within string"));
		  break;
		}
	      if (c == EOF)
		{
		  po_gram_error (_("end-of-file within string"));
		  break;
		}
	      if (c == '"')
		break;

	      if (c == '\\')
		c = control_sequence ();
	      if (bufpos >= bufmax)
		{
		  bufmax += 100;
		  buf = xrealloc (buf, bufmax);
		}
	      buf[bufpos++] = c;
	    }

	  if (bufpos >= bufmax)
	    {
	      bufmax += 100;
	      buf = xrealloc (buf, bufmax);
	    }
	  buf[bufpos] = 0;

	  po_gram_lval.string = xstrdup (buf);
	  return STRING;

	case 'a': case 'b': case 'c': case 'd': case 'e': case 'f':
	case 'g': case 'h': case 'i': case 'j': case 'k': case 'l':
	case 'm': case 'n': case 'o': case 'p': case 'q': case 'r':
	case 's': case 't': case 'u': case 'v': case 'w': case 'x':
	case 'y': case 'z':
	case 'A': case 'B': case 'C': case 'D': case 'E': case 'F':
	case 'G': case 'H': case 'I': case 'J': case 'K': case 'L':
	case 'M': case 'N': case 'O': case 'P': case 'Q': case 'R':
	case 'S': case 'T': case 'U': case 'V': case 'W': case 'X':
	case 'Y': case 'Z':
	case '_': case '$':
	  bufpos = 0;
	  for (;;)
	    {
	      if (bufpos >= bufmax)
		{
		  bufmax += 100;
		  buf = xrealloc (buf, bufmax);
		}
	      buf[bufpos++] = c;
	      c = lex_getc ();
	      switch (c)
		{
		default:
		  break;
		case 'a': case 'b': case 'c': case 'd':
		case 'e': case 'f': case 'g': case 'h':
		case 'i': case 'j': case 'k': case 'l':
		case 'm': case 'n': case 'o': case 'p':
		case 'q': case 'r': case 's': case 't':
		case 'u': case 'v': case 'w': case 'x':
		case 'y': case 'z':
		case 'A': case 'B': case 'C': case 'D':
		case 'E': case 'F': case 'G': case 'H':
		case 'I': case 'J': case 'K': case 'L':
		case 'M': case 'N': case 'O': case 'P':
		case 'Q': case 'R': case 'S': case 'T':
		case 'U': case 'V': case 'W': case 'X':
		case 'Y': case 'Z':
		case '_': case '$':
		case '0': case '1': case '2': case '3':
		case '4': case '5': case '6': case '7':
		case '8': case '9':
		  continue;
		}
	      break;
	    }
	  lex_ungetc (c);

	  if (bufpos >= bufmax)
	    {
	      bufmax += 100;
	      buf = xrealloc (buf, bufmax);
	    }
	  buf[bufpos] = 0;

	  c = keyword_p (buf);
	  if (c == NAME)
	    po_gram_lval.string = xstrdup (buf);
	  return c;

	case '0': case '1': case '2': case '3': case '4':
	case '5': case '6': case '7': case '8': case '9':
	  /* I know, we don't need numbers, yet.  */
	  bufpos = 0;
	  for (;;)
	    {
	      if (bufpos >= bufmax)
		{
		  bufmax += 100;
		  buf = xrealloc (buf, bufmax + 1);
		}
	      buf[bufpos++] = c;
	      c = lex_getc ();
	      switch (c)
		{
		default:
		  break;

		case '0': case '1': case '2': case '3':
		case '4': case '5': case '6': case '7':
		case '8': case '9':
		  continue;
		}
	      break;
	    }
	  lex_ungetc (c);

	  if (bufpos >= bufmax)
	    {
	      bufmax += 100;
	      buf = xrealloc (buf, bufmax);
	    }
	  buf[bufpos] = 0;

	  po_gram_lval.number = atol (buf);
	  return NUMBER;

	default:
	  /* This will cause a syntax error.  */
	  return JUNK;
	}
    }
}


void
po_lex_pass_comments (flag)
     int flag;
{
  pass_comments = (flag != 0);
}
