/* mclex.c -- lexer for Windows mc files parser.
   Copyright (C) 2007-2022 Free Software Foundation, Inc.

   Written by Kai Tietz, Onevision.

   This file is part of GNU Binutils.

   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 3 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 Street - Fifth Floor, Boston, MA
   02110-1301, USA.  */

/* This is a lexer used by the Windows rc file parser.
   It basically just recognized a bunch of keywords.  */

#include "sysdep.h"
#include "bfd.h"
#include "bucomm.h"
#include "libiberty.h"
#include "safe-ctype.h"
#include "windmc.h"
#include "mcparse.h"

#include <assert.h>

/* Exported globals.  */
bool mclex_want_nl = false;
bool mclex_want_line = false;
bool mclex_want_filename = false;

/* Local globals.  */
static unichar *input_stream = NULL;
static unichar *input_stream_pos = NULL;
static int input_line = 1;
static const char *input_filename = NULL;

void
mc_set_content (const unichar *src)
{
  if (!src)
    return;
  input_stream = input_stream_pos = unichar_dup (src);
}

void
mc_set_inputfile (const char *name)
{
  if (! name || *name == 0)
    input_filename = "-";
  else
    {
      const char *s1 = strrchr (name, '/');
      const char *s2 = strrchr (name, '\\');

      if (! s1)
	s1 = s2;
      if (s1 && s2 && s1 < s2)
	s1 = s2;
      if (! s1)
	s1 = name;
      else
	s1++;
      s1 = xstrdup (s1);
      input_filename = s1;
    }
}

static void
show_msg (const char *kind, const char *msg, va_list argp)
{
  fprintf (stderr, "In %s at line %d: %s: ", input_filename, input_line, kind);
  vfprintf (stderr, msg, argp);
  fprintf (stderr, ".\n");
}

void
mc_warn (const char *s, ...)
{
  va_list argp;
  va_start (argp, s);
  show_msg ("warning", s, argp);
  va_end (argp);
}

void
mc_fatal (const char *s, ...)
{
  va_list argp;
  va_start (argp, s);
  show_msg ("fatal", s, argp);
  va_end (argp);
  xexit (1);
}


static void
mc_error (const char *s, ...)
{
  va_list argp;
  va_start (argp, s);
  show_msg ("parser", s, argp);
  va_end (argp);
}

void
yyerror (const char *s)
{
  mc_error (s);
}

static unichar *
get_diff (unichar *end, unichar *start)
{
  unichar *ret;
  unichar save = *end;

  *end = 0;
  ret = unichar_dup (start);
  *end = save;
  return ret;
}

static rc_uint_type
parse_digit (unichar ch)
{
  rc_uint_type base = 10, v = 0, c;

  if (ch == '0')
    {
      base = 8;
      switch (input_stream_pos[0])
	{
	case 'x': case 'X': base = 16; input_stream_pos++; break;
	case 'o': case 'O': base = 8; input_stream_pos++; break;
	case 'b': case 'B': base = 2; input_stream_pos++; break;
	}
    }
  else
    v = (rc_uint_type) (ch - '0');

  while ((ch = input_stream_pos[0]) != 0)
    {
      if (ch >= 'A' && ch <= 'F')
	c = (rc_uint_type) (ch - 'A') + 10;
      else if (ch >= 'a' && ch <= 'f')
	c = (rc_uint_type) (ch - 'a') + 10;
      else if (ch >= '0' && ch <= '9')
	c = (rc_uint_type) (ch - '0');
      else
	break;
      v *= base;
      v += c;
      ++input_stream_pos;
    }
  if (input_stream_pos[0] == 'U' || input_stream_pos[0] == 'u')
    input_stream_pos++;
  if (input_stream_pos[0] == 'L' || input_stream_pos[0] == 'l')
    input_stream_pos++;
  if (input_stream_pos[0] == 'L' || input_stream_pos[0] == 'l')
    input_stream_pos++;
  return v;
}

static mc_keyword *keyword_top = NULL;

const mc_keyword *
enum_facility (int e)
{
  mc_keyword *h = keyword_top;

  while (h != NULL)
    {
      while (h && strcmp (h->group_name, "facility") != 0)
	h = h->next;
      if (e == 0)
	return h;
      --e;
      if (h)
	h = h->next;
    }
  return h;
}

const mc_keyword *
enum_severity (int e)
{
  mc_keyword *h = keyword_top;

  while (h != NULL)
    {
      while (h && strcmp (h->group_name, "severity") != 0)
	h = h->next;
      if (e == 0)
	return h;
      --e;
      if (h)
	h = h->next;
    }
  return h;
}

static void
mc_add_keyword_ascii (const char *sz, int rid, const char *grp, rc_uint_type nv, const char *sv)
{
  unichar *usz = NULL, *usv = NULL;
  rc_uint_type usz_len;

  unicode_from_codepage (&usz_len, &usz, sz, CP_ACP);
  if (sv)
    unicode_from_codepage (&usz_len, &usv, sv, CP_ACP);
  mc_add_keyword (usz, rid, grp, nv, usv);
}

void
mc_add_keyword (unichar *usz, int rid, const char *grp, rc_uint_type nv, unichar *sv)
{
  mc_keyword *p, *c, *n;
  size_t len = unichar_len (usz);

  c = keyword_top;
  p = NULL;
  while (c != NULL)
    {
      if (c->len > len)
	break;
      if (c->len == len)
	{
	  int e = memcmp (usz, c->usz, len * sizeof (unichar));

	  if (e < 0)
	    break;
	  if (! e)
	    {
	      if (! strcmp (grp, "keyword") || strcmp (c->group_name, grp) != 0)
		fatal (_("Duplicate symbol entered into keyword list."));
	      c->rid = rid;
	      c->nval = nv;
	      c->sval = (!sv ? NULL : unichar_dup (sv));
	      if (! strcmp (grp, "language"))
		{
		  const wind_language_t *lag = wind_find_language_by_id ((unsigned) nv);

		  if (lag == NULL)
		    fatal ("Language ident 0x%lx is not resolvable.\n", (long) nv);
		  memcpy (&c->lang_info, lag, sizeof (*lag));
		}
	      return;
	    }
	}
      c = (p = c)->next;
    }
  n = xmalloc (sizeof (mc_keyword));
  n->next = c;
  n->len = len;
  n->group_name = grp;
  n->usz = usz;
  n->rid = rid;
  n->nval = nv;
  n->sval = (!sv ? NULL : unichar_dup (sv));
  if (! strcmp (grp, "language"))
    {
      const wind_language_t *lag = wind_find_language_by_id ((unsigned) nv);
      if (lag == NULL)
	fatal ("Language ident 0x%lx is not resolvable.\n", (long) nv);
      memcpy (&n->lang_info, lag, sizeof (*lag));
    }
  if (! p)
    keyword_top = n;
  else
    p->next = n;
}

static int
mc_token (const unichar *t, size_t len)
{
  static int was_init = 0;
  mc_keyword *k;

  if (! was_init)
    {
      was_init = 1;
      mc_add_keyword_ascii ("OutputBase", MCOUTPUTBASE, "keyword", 0, NULL);
      mc_add_keyword_ascii ("MessageIdTypedef", MCMESSAGEIDTYPEDEF, "keyword", 0, NULL);
      mc_add_keyword_ascii ("SeverityNames", MCSEVERITYNAMES, "keyword", 0, NULL);
      mc_add_keyword_ascii ("FacilityNames", MCFACILITYNAMES, "keyword", 0, NULL);
      mc_add_keyword_ascii ("LanguageNames", MCLANGUAGENAMES, "keyword", 0, NULL);
      mc_add_keyword_ascii ("MessageId", MCMESSAGEID, "keyword", 0, NULL);
      mc_add_keyword_ascii ("Severity", MCSEVERITY, "keyword", 0, NULL);
      mc_add_keyword_ascii ("Facility", MCFACILITY, "keyword", 0, NULL);
      mc_add_keyword_ascii ("SymbolicName", MCSYMBOLICNAME, "keyword", 0, NULL);
      mc_add_keyword_ascii ("Language", MCLANGUAGE, "keyword", 0, NULL);
      mc_add_keyword_ascii ("Success", MCTOKEN, "severity", 0, NULL);
      mc_add_keyword_ascii ("Informational", MCTOKEN, "severity", 1, NULL);
      mc_add_keyword_ascii ("Warning", MCTOKEN, "severity", 2, NULL);
      mc_add_keyword_ascii ("Error", MCTOKEN, "severity", 3, NULL);
      mc_add_keyword_ascii ("System", MCTOKEN, "facility", 0xff, NULL);
      mc_add_keyword_ascii ("Application", MCTOKEN, "facility", 0xfff, NULL);
      mc_add_keyword_ascii ("English", MCTOKEN, "language", 0x409, "MSG00001");
  }
  k = keyword_top;
  if (!len || !t || *t == 0)
    return -1;
  while (k != NULL)
    {
      if (k->len > len)
	break;
      if (k->len == len)
	{
	  if (! memcmp (k->usz, t, len * sizeof (unichar)))
	    {
	      if (k->rid == MCTOKEN)
		yylval.tok = k;
	      return k->rid;
	    }
	}
      k = k->next;
    }
  return -1;
}

/* Skip characters in input_stream_pos up to and including a newline
   character.  Returns non-zero if the newline was found, zero otherwise.  */

static int
skip_until_eol (void)
{
  while (input_stream_pos[0] != 0 && input_stream_pos[0] != '\n')
    ++input_stream_pos;
  if (input_stream_pos[0] == 0)
    return 0;
  if (input_stream_pos[0] == '\n')
    {
      ++input_stream_pos;
      input_line += 1;
    }
  return 1;
}

int
yylex (void)
{
  unichar *start_token;
  unichar ch;

  if (! input_stream_pos)
    {
      fatal ("Input stream not setuped.\n");
      return -1;
    }

  if (mclex_want_line)
    {
      start_token = input_stream_pos;
      if (input_stream_pos[0] == 0)
	return -1;
      /* PR 26082: Reject a period followed by EOF.  */
      if (input_stream_pos[0] == '.' && input_stream_pos[1] == 0)
	return -1;
      if (input_stream_pos[0] == '.'
	  && (input_stream_pos[1] == '\n'
	      || (input_stream_pos[1] == '\r' && input_stream_pos[2] == '\n')))
	{
	  mclex_want_line = false;
          return skip_until_eol () ? MCENDLINE : -1;
	}
      if (!skip_until_eol ())
	return -1;
      yylval.ustr = get_diff (input_stream_pos, start_token);
      return MCLINE;
    }

  while ((ch = input_stream_pos[0]) <= 0x20)
    {
      if (ch == 0)
	return -1;
      ++input_stream_pos;
      if (ch == '\n')
	input_line += 1;
      if (mclex_want_nl && ch == '\n')
	{
	  mclex_want_nl = false;
	  return NL;
	}
    }
  start_token = input_stream_pos;
  ++input_stream_pos;
  if (mclex_want_filename)
    {
      mclex_want_filename = false;
      if (ch == '"')
	{
	  start_token++;
	  while ((ch = input_stream_pos[0]) != 0)
	    {
	      if (ch == '"')
		break;
	      ++input_stream_pos;
	    }
	  yylval.ustr = get_diff (input_stream_pos, start_token);
	  if (ch == '"')
	    ++input_stream_pos;
	}
      else
	{
	  while ((ch = input_stream_pos[0]) != 0)
	    {
	      if (ch <= 0x20 || ch == ')')
		break;
	      ++input_stream_pos;
	    }
	  yylval.ustr = get_diff (input_stream_pos, start_token);
	}
      return MCFILENAME;
    }
  switch (ch)
  {
  case ';':
    ++start_token;
    if (!skip_until_eol ())
      return -1;
    yylval.ustr = get_diff (input_stream_pos, start_token);
    return MCCOMMENT;
  case '=':
    return '=';
  case '(':
    return '(';
  case ')':
    return ')';
  case '+':
    return '+';
  case ':':
    return ':';
  case '0': case '1': case '2': case '3': case '4':
  case '5': case '6': case '7': case '8': case '9':
    yylval.ival = parse_digit (ch);
    return MCNUMBER;
  default:
    if (ch >= 0x40)
      {
	int ret;
	while (input_stream_pos[0] >= 0x40 || (input_stream_pos[0] >= '0' && input_stream_pos[0] <= '9'))
	  ++input_stream_pos;
	ret = mc_token (start_token, (size_t) (input_stream_pos - start_token));
	if (ret != -1)
	  return ret;
	yylval.ustr = get_diff (input_stream_pos, start_token);
	return MCIDENT;
      }
    mc_error ("illegal character 0x%x.", ch);
  }
  return -1;
}
