%{ /* rclex.l -- lexer for Windows rc files parser  */
/* Copyright 1997, 1998, 1999, 2001, 2002, 2003, 2005
   Free Software Foundation, Inc.
   Written by Ian Lance Taylor, Cygnus Support.

   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 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.  */

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

#include "bfd.h"
#include "bucomm.h"
#include "libiberty.h"
#include "safe-ctype.h"
#include "windres.h"
#include "rcparse.h"

#include <assert.h>

#define YY_NO_UNPUT

/* Whether we are in rcdata mode, in which we returns the lengths of
   strings.  */

static int rcdata_mode;

/* Whether we are supressing lines from cpp (including windows.h or
   headers from your C sources may bring in externs and typedefs).
   When active, we return IGNORED_TOKEN, which lets us ignore these
   outside of resource constructs.  Thus, it isn't required to protect
   all the non-preprocessor lines in your header files with #ifdef
   RC_INVOKED.  It also means your RC file can't include other RC
   files if they're named "*.h".  Sorry.  Name them *.rch or whatever.  */

static int suppress_cpp_data;

#define MAYBE_RETURN(x) return suppress_cpp_data ? IGNORED_TOKEN : (x)

/* The first filename we detect in the cpp output.  We use this to
   tell included files from the original file.  */

static char *initial_fn;

/* List of allocated strings.  */

struct alloc_string
{
  struct alloc_string *next;
  char *s;
};

static struct alloc_string *strings;

/* Local functions.  */

static void cpp_line (const char *);
static char *handle_quotes (const char *, unsigned long *);
static char *get_string (int);

%}

%%

"BEGIN"			{ MAYBE_RETURN (BEG); }
"{"			{ MAYBE_RETURN (BEG); }
"END"			{ MAYBE_RETURN (END); }
"}"			{ MAYBE_RETURN (END); }
"ACCELERATORS"		{ MAYBE_RETURN (ACCELERATORS); }
"VIRTKEY"		{ MAYBE_RETURN (VIRTKEY); }
"ASCII"			{ MAYBE_RETURN (ASCII); }
"NOINVERT"		{ MAYBE_RETURN (NOINVERT); }
"SHIFT"			{ MAYBE_RETURN (SHIFT); }
"CONTROL"		{ MAYBE_RETURN (CONTROL); }
"ALT"			{ MAYBE_RETURN (ALT); }
"BITMAP"		{ MAYBE_RETURN (BITMAP); }
"CURSOR"		{ MAYBE_RETURN (CURSOR); }
"DIALOG"		{ MAYBE_RETURN (DIALOG); }
"DIALOGEX"		{ MAYBE_RETURN (DIALOGEX); }
"EXSTYLE"		{ MAYBE_RETURN (EXSTYLE); }
"CAPTION"		{ MAYBE_RETURN (CAPTION); }
"CLASS"			{ MAYBE_RETURN (CLASS); }
"STYLE"			{ MAYBE_RETURN (STYLE); }
"AUTO3STATE"		{ MAYBE_RETURN (AUTO3STATE); }
"AUTOCHECKBOX"		{ MAYBE_RETURN (AUTOCHECKBOX); }
"AUTORADIOBUTTON"	{ MAYBE_RETURN (AUTORADIOBUTTON); }
"CHECKBOX"		{ MAYBE_RETURN (CHECKBOX); }
"COMBOBOX"		{ MAYBE_RETURN (COMBOBOX); }
"CTEXT"			{ MAYBE_RETURN (CTEXT); }
"DEFPUSHBUTTON"		{ MAYBE_RETURN (DEFPUSHBUTTON); }
"EDITTEXT"		{ MAYBE_RETURN (EDITTEXT); }
"GROUPBOX"		{ MAYBE_RETURN (GROUPBOX); }
"LISTBOX"		{ MAYBE_RETURN (LISTBOX); }
"LTEXT"			{ MAYBE_RETURN (LTEXT); }
"PUSHBOX"		{ MAYBE_RETURN (PUSHBOX); }
"PUSHBUTTON"		{ MAYBE_RETURN (PUSHBUTTON); }
"RADIOBUTTON"		{ MAYBE_RETURN (RADIOBUTTON); }
"RTEXT"			{ MAYBE_RETURN (RTEXT); }
"SCROLLBAR"		{ MAYBE_RETURN (SCROLLBAR); }
"STATE3"		{ MAYBE_RETURN (STATE3); }
"USERBUTTON"		{ MAYBE_RETURN (USERBUTTON); }
"BEDIT"			{ MAYBE_RETURN (BEDIT); }
"HEDIT"			{ MAYBE_RETURN (HEDIT); }
"IEDIT"			{ MAYBE_RETURN (IEDIT); }
"FONT"			{ MAYBE_RETURN (FONT); }
"ICON"			{ MAYBE_RETURN (ICON); }
"LANGUAGE"		{ MAYBE_RETURN (LANGUAGE); }
"CHARACTERISTICS"	{ MAYBE_RETURN (CHARACTERISTICS); }
"VERSION"		{ MAYBE_RETURN (VERSIONK); }
"MENU"			{ MAYBE_RETURN (MENU); }
"MENUEX"		{ MAYBE_RETURN (MENUEX); }
"MENUITEM"		{ MAYBE_RETURN (MENUITEM); }
"SEPARATOR"		{ MAYBE_RETURN (SEPARATOR); }
"POPUP"			{ MAYBE_RETURN (POPUP); }
"CHECKED"		{ MAYBE_RETURN (CHECKED); }
"GRAYED"		{ MAYBE_RETURN (GRAYED); }
"HELP"			{ MAYBE_RETURN (HELP); }
"INACTIVE"		{ MAYBE_RETURN (INACTIVE); }
"MENUBARBREAK"		{ MAYBE_RETURN (MENUBARBREAK); }
"MENUBREAK"		{ MAYBE_RETURN (MENUBREAK); }
"MESSAGETABLE"		{ MAYBE_RETURN (MESSAGETABLE); }
"RCDATA"		{ MAYBE_RETURN (RCDATA); }
"STRINGTABLE"		{ MAYBE_RETURN (STRINGTABLE); }
"VERSIONINFO"		{ MAYBE_RETURN (VERSIONINFO); }
"FILEVERSION"		{ MAYBE_RETURN (FILEVERSION); }
"PRODUCTVERSION"	{ MAYBE_RETURN (PRODUCTVERSION); }
"FILEFLAGSMASK"		{ MAYBE_RETURN (FILEFLAGSMASK); }
"FILEFLAGS"		{ MAYBE_RETURN (FILEFLAGS); }
"FILEOS"		{ MAYBE_RETURN (FILEOS); }
"FILETYPE"		{ MAYBE_RETURN (FILETYPE); }
"FILESUBTYPE"		{ MAYBE_RETURN (FILESUBTYPE); }
"VALUE"			{ MAYBE_RETURN (VALUE); }
"MOVEABLE"		{ MAYBE_RETURN (MOVEABLE); }
"FIXED"			{ MAYBE_RETURN (FIXED); }
"PURE"			{ MAYBE_RETURN (PURE); }
"IMPURE"		{ MAYBE_RETURN (IMPURE); }
"PRELOAD"		{ MAYBE_RETURN (PRELOAD); }
"LOADONCALL"		{ MAYBE_RETURN (LOADONCALL); }
"DISCARDABLE"		{ MAYBE_RETURN (DISCARDABLE); }
"NOT"			{ MAYBE_RETURN (NOT); }

"BLOCK"[ \t\n]*"\""[^\#\n]*"\"" {
			  char *s, *send;

			  /* This is a hack to let us parse version
                             information easily.  */

			  s = strchr (yytext, '"');
			  ++s;
			  send = strchr (s, '"');
			  if (strncmp (s, "StringFileInfo",
				       sizeof "StringFileInfo" - 1) == 0
			      && s + sizeof "StringFileInfo" - 1 == send)
			    MAYBE_RETURN (BLOCKSTRINGFILEINFO);
			  else if (strncmp (s, "VarFileInfo",
					    sizeof "VarFileInfo" - 1) == 0
				   && s + sizeof "VarFileInfo" - 1 == send)
			    MAYBE_RETURN (BLOCKVARFILEINFO);
			  else
			    {
			      char *r;

			      r = get_string (send - s + 1);
			      strncpy (r, s, send - s);
			      r[send - s] = '\0';
			      yylval.s = r;
			      MAYBE_RETURN (BLOCK);
			    }
			}

"#"[^\n]*		{
			  cpp_line (yytext);
			}

[0-9][x0-9A-Fa-f]*L	{
			  yylval.i.val = strtoul (yytext, 0, 0);
			  yylval.i.dword = 1;
			  MAYBE_RETURN (NUMBER);
			}

[0-9][x0-9A-Fa-f]*	{
			  yylval.i.val = strtoul (yytext, 0, 0);
			  yylval.i.dword = 0;
			  MAYBE_RETURN (NUMBER);
			}

("\""[^\"\n]*"\""[ \t\n]*)+ {
			  char *s;
			  unsigned long length;

			  s = handle_quotes (yytext, &length);
			  if (! rcdata_mode)
			    {
			      yylval.s = s;
			      MAYBE_RETURN (QUOTEDSTRING);
			    }
			  else
			    {
			      yylval.ss.length = length;
			      yylval.ss.s = s;
			      MAYBE_RETURN (SIZEDSTRING);
			    }
			}

[A-Za-z][^ ,\t\r\n]*	{
			  char *s;

			  /* I rejected comma in a string in order to
			     handle VIRTKEY, CONTROL in an accelerator
			     resource.  This means that an unquoted
			     file name can not contain a comma.  I
			     don't know what rc permits.  */

			  s = get_string (strlen (yytext) + 1);
			  strcpy (s, yytext);
			  yylval.s = s;
			  MAYBE_RETURN (STRING);
			}

[\n]			{ ++rc_lineno; }
[ \t\r]+		{ /* ignore whitespace */ }
.			{ MAYBE_RETURN (*yytext); }

%%
#ifndef yywrap
/* This is needed for some versions of lex.  */
int yywrap (void)
{
  return 1;
}
#endif

/* Handle a C preprocessor line.  */

static void
cpp_line (const char *s)
{
  int line;
  char *send, *fn;

  ++s;
  while (ISSPACE (*s))
    ++s;
  
  line = strtol (s, &send, 0);
  if (*send != '\0' && ! ISSPACE (*send))
    return;

  /* Subtract 1 because we are about to count the newline.  */
  rc_lineno = line - 1;

  s = send;
  while (ISSPACE (*s))
    ++s;

  if (*s != '"')
    return;

  ++s;
  send = strchr (s, '"');
  if (send == NULL)
    return;

  fn = (char *) xmalloc (send - s + 1);
  strncpy (fn, s, send - s);
  fn[send - s] = '\0';

  free (rc_filename);
  rc_filename = fn;

  if (!initial_fn)
    {
      initial_fn = xmalloc (strlen (fn) + 1);
      strcpy (initial_fn, fn);
    }

  /* Allow the initial file, regardless of name.  Suppress all other
     files if they end in ".h" (this allows included "*.rc").  */
  if (strcmp (initial_fn, fn) == 0
      || strcmp (fn + strlen (fn) - 2, ".h") != 0)
    suppress_cpp_data = 0;
  else
    suppress_cpp_data = 1;
}

/* Handle a quoted string.  The quotes are stripped.  A pair of quotes
   in a string are turned into a single quote.  Adjacent strings are
   merged separated by whitespace are merged, as in C.  */

static char *
handle_quotes (const char *input, unsigned long *len)
{
  char *ret, *s;
  const char *t;
  int ch;

  ret = get_string (strlen (input) + 1);

  s = ret;
  t = input;
  if (*t == '"')
    ++t;
  while (*t != '\0')
    {
      if (*t == '\\')
	{
	  ++t;
	  switch (*t)
	    {
	    case '\0':
	      rcparse_warning ("backslash at end of string");
	      break;

	    case '\"':
	      rcparse_warning ("use \"\" to put \" in a string");
	      break;

	    case 'a':
	      *s++ = ESCAPE_B; /* Strange, but true...  */
	      ++t;
	      break;

	    case 'b':
	      *s++ = ESCAPE_B;
	      ++t;
	      break;

	    case 'f':
	      *s++ = ESCAPE_F;
	      ++t;
	      break;

	    case 'n':
	      *s++ = ESCAPE_N;
	      ++t;
	      break;

	    case 'r':
	      *s++ = ESCAPE_R;
	      ++t;
	      break;

	    case 't':
	      *s++ = ESCAPE_T;
	      ++t;
	      break;

	    case 'v':
	      *s++ = ESCAPE_V;
	      ++t;
	      break;

	    case '\\':
	      *s++ = *t++;
	      break;

	    case '0': case '1': case '2': case '3':
	    case '4': case '5': case '6': case '7':
	      ch = *t - '0';
	      ++t;
	      if (*t >= '0' && *t <= '7')
		{
		  ch = (ch << 3) | (*t - '0');
		  ++t;
		  if (*t >= '0' && *t <= '7')
		    {
		      ch = (ch << 3) | (*t - '0');
		      ++t;
		    }
		}
	      *s++ = ch;
	      break;

	    case 'x':
	      ++t;
	      ch = 0;
	      while (1)
		{
		  if (*t >= '0' && *t <= '9')
		    ch = (ch << 4) | (*t - '0');
		  else if (*t >= 'a' && *t <= 'f')
		    ch = (ch << 4) | (*t - 'a' + 10);
		  else if (*t >= 'A' && *t <= 'F')
		    ch = (ch << 4) | (*t - 'A' + 10);
		  else
		    break;
		  ++t;
		}
	      *s++ = ch;
	      break;

	    default:
	      rcparse_warning ("unrecognized escape sequence");
	      *s++ = '\\';
	      *s++ = *t++;
	      break;
	    }
	}
      else if (*t != '"')
	*s++ = *t++;
      else if (t[1] == '\0')
	break;
      else if (t[1] == '"')
	{
	  *s++ = '"';
	  t += 2;
	}
      else
	{
	  ++t;
	  assert (ISSPACE (*t));
	  while (ISSPACE (*t))
	    {
	      if ((*t) == '\n')
		++rc_lineno;
	      ++t;
	    }
	  if (*t == '\0')
	    break;
	  assert (*t == '"');
	  ++t;
	}
    }

  *s = '\0';

  *len = s - ret;

  return ret;
}

/* Allocate a string of a given length.  */

static char *
get_string (int len)
{
  struct alloc_string *as;

  as = (struct alloc_string *) xmalloc (sizeof *as);
  as->s = xmalloc (len);

  as->next = strings;
  strings = as;

  return as->s;
}

/* Discard all the strings we have allocated.  The parser calls this
   when it no longer needs them.  */

void
rcparse_discard_strings (void)
{
  struct alloc_string *as;

  as = strings;
  while (as != NULL)
    {
      struct alloc_string *n;

      free (as->s);
      n = as->next;
      free (as);
      as = n;
    }

  strings = NULL;
}

/* Enter rcdata mode.  */

void
rcparse_rcdata (void)
{
  rcdata_mode = 1;
}

/* Go back to normal mode from rcdata mode.  */

void
rcparse_normal (void)
{
  rcdata_mode = 0;
}
