%{ /* rclex.l -- lexer for Windows rc files parser  */
/* Copyright 1997 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 "windres.h"
#include "rcparse.h"

#include <ctype.h>
#include <assert.h>

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

static int rcdata_mode;

/* List of allocated strings.  */

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

static struct alloc_string *strings;

/* Local functions.  */

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

%}

%%

"BEGIN"			{ return BEG; }
"END"			{ return END; }
"ACCELERATORS"		{ return ACCELERATORS; }
"VIRTKEY"		{ return VIRTKEY; }
"ASCII"			{ return ASCII; }
"NOINVERT"		{ return NOINVERT; }
"SHIFT"			{ return SHIFT; }
"CONTROL"		{ return CONTROL; }
"ALT"			{ return ALT; }
"BITMAP"		{ return BITMAP; }
"CURSOR"		{ return CURSOR; }
"DIALOG"		{ return DIALOG; }
"DIALOGEX"		{ return DIALOGEX; }
"EXSTYLE"		{ return EXSTYLE; }
"CAPTION"		{ return CAPTION; }
"CLASS"			{ return CLASS; }
"STYLE"			{ return STYLE; }
"AUTO3STATE"		{ return AUTO3STATE; }
"AUTOCHECKBOX"		{ return AUTOCHECKBOX; }
"AUTORADIOBUTTON"	{ return AUTORADIOBUTTON; }
"CHECKBOX"		{ return CHECKBOX; }
"COMBOBOX"		{ return COMBOBOX; }
"CTEXT"			{ return CTEXT; }
"DEFPUSHBUTTON"		{ return DEFPUSHBUTTON; }
"EDITTEXT"		{ return EDITTEXT; }
"GROUPBOX"		{ return GROUPBOX; }
"LISTBOX"		{ return LISTBOX; }
"LTEXT"			{ return LTEXT; }
"PUSHBOX"		{ return PUSHBOX; }
"PUSHBUTTON"		{ return PUSHBUTTON; }
"RADIOBUTTON"		{ return RADIOBUTTON; }
"RTEXT"			{ return RTEXT; }
"SCROLLBAR"		{ return SCROLLBAR; }
"STATE3"		{ return STATE3; }
"USERBUTTON"		{ return USERBUTTON; }
"BEDIT"			{ return BEDIT; }
"HEDIT"			{ return HEDIT; }
"IEDIT"			{ return IEDIT; }
"FONT"			{ return FONT; }
"ICON"			{ return ICON; }
"LANGUAGE"		{ return LANGUAGE; }
"CHARACTERISTICS"	{ return CHARACTERISTICS; }
"VERSION"		{ return VERSIONK; }
"MENU"			{ return MENU; }
"MENUEX"		{ return MENUEX; }
"MENUITEM"		{ return MENUITEM; }
"SEPARATOR"		{ return SEPARATOR; }
"POPUP"			{ return POPUP; }
"CHECKED"		{ return CHECKED; }
"GRAYED"		{ return GRAYED; }
"HELP"			{ return HELP; }
"INACTIVE"		{ return INACTIVE; }
"MENUBARBREAK"		{ return MENUBARBREAK; }
"MENUBREAK"		{ return MENUBREAK; }
"MESSAGETABLE"		{ return MESSAGETABLE; }
"RCDATA"		{ return RCDATA; }
"STRINGTABLE"		{ return STRINGTABLE; }
"VERSIONINFO"		{ return VERSIONINFO; }
"FILEVERSION"		{ return FILEVERSION; }
"PRODUCTVERSION"	{ return PRODUCTVERSION; }
"FILEFLAGSMASK"		{ return FILEFLAGSMASK; }
"FILEFLAGS"		{ return FILEFLAGS; }
"FILEOS"		{ return FILEOS; }
"FILETYPE"		{ return FILETYPE; }
"FILESUBTYPE"		{ return FILESUBTYPE; }
"VALUE"			{ return VALUE; }
"MOVEABLE"		{ return MOVEABLE; }
"FIXED"			{ return FIXED; }
"PURE"			{ return PURE; }
"IMPURE"		{ return IMPURE; }
"PRELOAD"		{ return PRELOAD; }
"LOADONCALL"		{ return LOADONCALL; }
"DISCARDABLE"		{ return DISCARDABLE; }
"NOT"			{ 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)
			    return BLOCKSTRINGFILEINFO;
			  else if (strncmp (s, "VarFileInfo",
					    sizeof "VarFileInfo" - 1) == 0
				   && s + sizeof "VarFileInfo" - 1 == send)
			    return BLOCKVARFILEINFO;
			  else
			    {
			      char *r;

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

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

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

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

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

			  s = handle_quotes (yytext, &length);
			  if (! rcdata_mode)
			    {
			      yylval.s = s;
			      return QUOTEDSTRING;
			    }
			  else
			    {
			      yylval.ss.length = length;
			      yylval.ss.s = s;
			      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;
			  return STRING;
			}

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

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

/* Handle a C preprocessor line.  */

static void
cpp_line (s)
     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;
}

/* 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 (input, len)
     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_A;
	      ++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');
		  else if (*t >= 'A' && *t <= 'F')
		    ch = (ch << 4) | (*t - 'A');
		  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))
	    ++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 (len)
     int len;
{
  struct alloc_string *as;

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

  as->next = strings;
  strings = as->next;

  return as->s;
}

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

void
rcparse_discard_strings ()
{
  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 ()
{
  rcdata_mode = 1;
}

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

void
rcparse_normal ()
{
  rcdata_mode = 0;
}