%{ /* rclex.l -- lexer for Windows rc files parser  */
/* Copyright 1997, 1998 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 <ctype.h>
#include <assert.h>
#include <string.h>

#include <res.h>
#include "y.tab.h"
#include "rc.h"

#if defined(__sgi) && !defined(__GNUC__)
#pragma set woff 1110
#endif

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

int rc_lineno;
char *rc_filename;

/* Local functions.  */

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

%}

%%

"BEGIN"           { return BEG; }
"{"               { return BEG; }
"END"             { return 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; }
"TOOLBAR"         { return TOOLBAR; }
"BUTTON"          { return BUTTON; }
"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; }
"ES_READONLY"     { return ES_READONLY; }
"ES_MULTILINE"    { return ES_MULTILINE; }
"ES_PASSWORD"     { return ES_PASSWORD; }
"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; }
"DLGINIT"         { return DLGINIT; }

"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 = strtoul (yytext, 0, 0);
                          return NUMBER;
                        }

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

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

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

            case '?':
              *s++ = '?';
              ++t;
              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++ = '\\';
          *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 *) malloc (sizeof *as);
  as->s = (char *) malloc (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;
}
