%{
/* yylex.l  The scripting lexer.  */
/*
 *  GRUB  --  GRand Unified Bootloader
 *  Copyright (C) 2009,2010  Free Software Foundation, Inc.
 *
 *  GRUB 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.
 *
 *  GRUB 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 GRUB.  If not, see <http://www.gnu.org/licenses/>.
 */

#include <grub/parser.h>
#include <grub/misc.h>
#include <grub/mm.h>
#include <grub/script_sh.h>
#include <grub/i18n.h>
#include "grub_script.tab.h"

#pragma GCC diagnostic ignored "-Wunused-parameter"
#pragma GCC diagnostic ignored "-Wmissing-prototypes"
#pragma GCC diagnostic ignored "-Wmissing-declarations"
#pragma GCC diagnostic ignored "-Wunsafe-loop-optimizations"
#pragma GCC diagnostic ignored "-Wunused-function"
#pragma GCC diagnostic ignored "-Wsign-compare"

#define yyfree    grub_lexer_yyfree
#define yyalloc   grub_lexer_yyalloc
#define yyrealloc grub_lexer_yyrealloc

/* 
 * As we don't have access to yyscanner, we cannot do much except to
 * print the fatal error.
 */
#define YY_FATAL_ERROR(msg)                     \
  do {                                          \
    grub_printf (_("fatal error: %s\n"), _(msg));     \
  } while (0)

#define COPY(str, hint)                         \
  do {                                          \
    copy_string (yyextra, str, hint);           \
  } while (0)


#define RECORD                                  \
  do {                                          \
    grub_script_lexer_record (yyextra, yytext); \
  } while (0)

#define ARG(t)                        \
  do {                                \
    yyextra->lexerstate->type = t;    \
    return GRUB_PARSER_TOKEN_WORD;    \
  } while (0)

/* We don't need YY_INPUT, as we rely on yy_scan_strings */
#define YY_INPUT(buf,res,max) do { res = 0; } while (0)

/* forward declarations */
static int grub_lexer_unput (const char *input, yyscan_t yyscanner);
static int grub_lexer_resplit (const char *input, yyscan_t yyscanner);

static void  grub_lexer_yyfree (void *, yyscan_t yyscanner);
static void* grub_lexer_yyalloc (yy_size_t, yyscan_t yyscanner);
static void* grub_lexer_yyrealloc (void*, yy_size_t, yyscan_t yyscanner);
static void  copy_string (struct grub_parser_param *, const char *,
                          unsigned hint);

%}

%top{

#include <config.h>

#include <sys/types.h>

typedef size_t yy_size_t;
#define YY_TYPEDEF_YY_SIZE_T 1

/* 
 * Some flex hacks for -nostdinc; XXX We need to fix these when libc
 * support becomes availble in GRUB.
 */

#ifndef GRUB_UTIL
#define stdin  0
#define stdout 0

#define fprintf(...) 0
#define exit(...)
#endif

}

%option ecs
%option meta-ecs

%option warn
%option array
%option stack
%option reentrant
%option bison-bridge
%option never-interactive

%option noyyfree noyyalloc noyyrealloc
%option nounistd nostdinit nodefault noyylineno

/* Reduce lexer size, by not defining these.  */
%option noyy_top_state
%option noinput nounput
%option noyyget_in noyyset_in
%option noyyget_out noyyset_out
%option noyyget_debug noyyset_debug
%option noyyget_lineno noyyset_lineno

%option extra-type="struct grub_parser_param*"

BLANK           [ \t]
COMMENT         #.*$

CHAR            [^{}|&$;<> \t\n\'\"\\]
DIGITS          [[:digit:]]+
NAME            [[:alpha:]_][[:alnum:]_]*

ESC             \\(.|\n)
SQCHR           [^\']
DQCHR           {ESC}|[^\\\"]
DQSTR           \"{DQCHR}*\"
I18NSTR         \$\"{DQCHR}*\"
SQSTR           \'{SQCHR}*\'
SPECIAL         \?|\#|\*|\@
VARIABLE        ${NAME}|$\{{NAME}\}|${DIGITS}|$\{{DIGITS}\}|${SPECIAL}|$\{{SPECIAL}\}
WORD            ({CHAR}|{DQSTR}|{SQSTR}|{ESC}|{VARIABLE}|{I18NSTR})+

MULTILINE       {WORD}?((\"{DQCHR}*)|(\$\"{DQCHR}*)|(\'{SQCHR}*))
POS_MULTILINE   {WORD}?\\\n

%x              SPLIT
%x              DQUOTE
%x              I18NQUOTE
%x              SQUOTE
%x              VAR

%%

 /* White spaces */
{BLANK}+        { RECORD; }
{COMMENT}       { RECORD; }

 /* Special symbols */
"\n"            { RECORD; return GRUB_PARSER_TOKEN_NEWLINE; }
"||"            { RECORD; return GRUB_PARSER_TOKEN_OR;      }
"&&"            { RECORD; return GRUB_PARSER_TOKEN_AND;     }
";;"            { RECORD; return GRUB_PARSER_TOKEN_SEMI2;   }
"|"             { RECORD; return GRUB_PARSER_TOKEN_PIPE;    }
"&"             { RECORD; return GRUB_PARSER_TOKEN_AMP;     }
";"             { RECORD; return GRUB_PARSER_TOKEN_SEMI;    }
"<"             { RECORD; return GRUB_PARSER_TOKEN_LT;      }
">"             { RECORD; return GRUB_PARSER_TOKEN_GT;      }

 /* Reserved words */
"{"             { RECORD; return GRUB_PARSER_TOKEN_LBR;       }
"}"             { RECORD; return GRUB_PARSER_TOKEN_RBR;       }
"[["            { RECORD; return GRUB_PARSER_TOKEN_RSQBR2;    }
"]]"            { RECORD; return GRUB_PARSER_TOKEN_LSQBR2;    }
"case"          { RECORD; return GRUB_PARSER_TOKEN_CASE;      }
"do"            { RECORD; return GRUB_PARSER_TOKEN_DO;        }
"done"          { RECORD; return GRUB_PARSER_TOKEN_DONE;      }
"elif"          { RECORD; return GRUB_PARSER_TOKEN_ELIF;      }
"else"          { RECORD; return GRUB_PARSER_TOKEN_ELSE;      }
"esac"          { RECORD; return GRUB_PARSER_TOKEN_ESAC;      }
"fi"            { RECORD; return GRUB_PARSER_TOKEN_FI;        }
"for"           { RECORD; return GRUB_PARSER_TOKEN_FOR;       }
"if"            { RECORD; return GRUB_PARSER_TOKEN_IF;        }
"in"            { RECORD; return GRUB_PARSER_TOKEN_IN;        }
"select"        { RECORD; return GRUB_PARSER_TOKEN_SELECT;    }
"then"          { RECORD; return GRUB_PARSER_TOKEN_THEN;      }
"until"         { RECORD; return GRUB_PARSER_TOKEN_UNTIL;     }
"while"         { RECORD; return GRUB_PARSER_TOKEN_WHILE;     }
"function"      { RECORD; return GRUB_PARSER_TOKEN_FUNCTION;  }

{MULTILINE}     {
                  if (grub_lexer_unput (yytext, yyscanner))
		    return GRUB_PARSER_TOKEN_BAD;
		}

{POS_MULTILINE} {
                  if (yyg->yy_c_buf_p + 1 == &YY_CURRENT_BUFFER_LVALUE->yy_ch_buf[yyg->yy_n_chars + 1] )
		    {
		      if (grub_lexer_unput (yytext, yyscanner))
			return GRUB_PARSER_TOKEN_BAD;
		    }
		  else
		    {
		      RECORD;
		      yypush_buffer_state (YY_CURRENT_BUFFER, yyscanner);
		      if (grub_lexer_resplit (yytext, yyscanner))
			{
			  yypop_buffer_state (yyscanner);
			  return GRUB_PARSER_TOKEN_WORD;
			}
		      yyextra->lexerstate->resplit = 1;
		    }
		}


{NAME}          { RECORD; return GRUB_PARSER_TOKEN_NAME; }
{WORD}          {
                  RECORD;
		  yypush_buffer_state (YY_CURRENT_BUFFER, yyscanner);
		  if (grub_lexer_resplit (yytext, yyscanner))
		    {
		      yypop_buffer_state (yyscanner);
		      return GRUB_PARSER_TOKEN_WORD;
		    }
		  yyextra->lexerstate->resplit = 1;
                }
.               {
                  grub_script_yyerror (yyextra, yytext);
		  return GRUB_PARSER_TOKEN_BAD;
                }

 /* Split word into multiple args */

<SPLIT>{
  \\.           { COPY (yytext, yyleng); }
  \\\n          { /* ignore */ }
  \"            {
                  yy_push_state (DQUOTE, yyscanner);
                  ARG (GRUB_SCRIPT_ARG_TYPE_TEXT);
                }
  \'            {
                  yy_push_state (SQUOTE, yyscanner);
                  ARG (GRUB_SCRIPT_ARG_TYPE_TEXT);
                }
  "\$\""        {
                  yy_push_state (I18NQUOTE, yyscanner);
                  ARG (GRUB_SCRIPT_ARG_TYPE_GETTEXT);
                }
  \$            {
                  yy_push_state (VAR, yyscanner);
                  ARG (GRUB_SCRIPT_ARG_TYPE_TEXT);
                }
  \\            |
  [^\"\'\$\\]+  { COPY (yytext, yyleng); }
  <<EOF>>       {
                  yy_pop_state (yyscanner);
                  yypop_buffer_state (yyscanner);
		  yyextra->lexerstate->resplit = 0;
                  yyextra->lexerstate->merge_end = 1;
                  ARG (GRUB_SCRIPT_ARG_TYPE_TEXT);
                }
}

<VAR>{
  {SPECIAL}     |
  {DIGITS}      |
  {NAME}        {
                  COPY (yytext, yyleng);
                  yy_pop_state (yyscanner);
                  if (YY_START == SPLIT)
                    ARG (GRUB_SCRIPT_ARG_TYPE_VAR);
                  else
                    ARG (GRUB_SCRIPT_ARG_TYPE_DQVAR);
                }
  \{{SPECIAL}\} |
  \{{DIGITS}\}  |
  \{{NAME}\}    {
                  yytext[yyleng - 1] = '\0';
                  COPY (yytext + 1, yyleng - 2);
                  yy_pop_state (yyscanner);
                  if (YY_START == SPLIT)
                    ARG (GRUB_SCRIPT_ARG_TYPE_VAR);
                  else
                    ARG (GRUB_SCRIPT_ARG_TYPE_DQVAR);
                }
  .|\n          { return GRUB_PARSER_TOKEN_BAD; }
}

<SQUOTE>{
  \'            {
                  yy_pop_state (yyscanner);
                  ARG (GRUB_SCRIPT_ARG_TYPE_SQSTR);
                }
  [^\']+        { COPY (yytext, yyleng); }
}

<DQUOTE>{
  \\\$          { COPY ("$", 1); }
  \\\\          { COPY ("\\", 1); }
  \\\"          { COPY ("\"", 1); }
  \\\n          { /* ignore */ }
  [^\"\$\\\n]+  { COPY (yytext, yyleng); }
  \"            {
                  yy_pop_state (yyscanner);
                  ARG (GRUB_SCRIPT_ARG_TYPE_DQSTR);
                }
  \$            {
                  yy_push_state (VAR, yyscanner);
                  ARG (GRUB_SCRIPT_ARG_TYPE_DQSTR);
                }
  (.|\n)        { COPY (yytext, yyleng); }
}

<I18NQUOTE>{
  \\\\          { COPY ("\\\\", 2); }
  \\\"          { COPY ("\"", 1); }
  \\\n          { /* ignore */ }
  [^\"\\\n]+    { COPY (yytext, yyleng); }
  \"            {
                  yy_pop_state (yyscanner);
                  ARG (GRUB_SCRIPT_ARG_TYPE_GETTEXT);
                }
  \\            { COPY ("\\", 1); }
  (.|\n)        { COPY (yytext, yyleng); }
}

<<EOF>>         {
                  yypop_buffer_state (yyscanner);
		  yyextra->lexerstate->eof = 1;
		  return GRUB_PARSER_TOKEN_EOF;
                }
%%

int
yywrap (yyscan_t yyscanner)
{
  if (yyget_extra (yyscanner)->lexerstate->resplit)
    return 1;

  return grub_script_lexer_yywrap (yyget_extra (yyscanner), 0);
}

static void
grub_lexer_yyfree (void *ptr, yyscan_t yyscanner __attribute__ ((unused)))
{
  grub_free(ptr);
}

static void*
grub_lexer_yyalloc (yy_size_t size, yyscan_t yyscanner __attribute__ ((unused)))
{
  return grub_malloc (size);
}

static void*
grub_lexer_yyrealloc (void *ptr, yy_size_t size,
                      yyscan_t yyscanner __attribute__ ((unused)))
{
  return grub_realloc (ptr, size);
}

static void copy_string (struct grub_parser_param *parser, const char *str, unsigned hint)
{
  grub_size_t size;
  char *ptr;
  unsigned len;

  len = hint ? hint : grub_strlen (str);
  if (parser->lexerstate->used + len >= parser->lexerstate->size)
    {
      size = len * 2;
      if (size < parser->lexerstate->size * 2)
        size = parser->lexerstate->size * 2;
      ptr = grub_realloc (parser->lexerstate->text, size);
      if (!ptr)
        {
          grub_script_yyerror (parser, 0);
          return;
        }

      parser->lexerstate->text = ptr;
      parser->lexerstate->size = size;
    }
  grub_strcpy (parser->lexerstate->text + parser->lexerstate->used - 1, str);
  parser->lexerstate->used += len;
}

static int
grub_lexer_resplit (const char *text, yyscan_t yyscanner)
{
  /* resplit text */
  if (yy_scan_string (text, yyscanner))
    {
      yyget_extra (yyscanner)->lexerstate->merge_start = 1;
      yy_push_state (SPLIT, yyscanner);
      return 0;
    }
  grub_script_yyerror (yyget_extra (yyscanner), 0);
  return 1;
}

static int
grub_lexer_unput (const char *text, yyscan_t yyscanner)
{
  struct grub_lexer_param *lexerstate = yyget_extra (yyscanner)->lexerstate;

  grub_free (lexerstate->prefix);

  lexerstate->prefix = grub_strdup (text);
  if (! lexerstate->prefix)
    {
      grub_script_yyerror (yyget_extra (yyscanner), N_("out of memory"));
      return 1;
    }
  return 0;
}