%{
/* GNU Mailutils -- a suite of utilities for electronic mail
   Copyright (C) 1999, 2000, 2001, 2002, 2005, 2006, 2007, 2008, 2009,
   2010 Free Software Foundation, Inc.

   This library is free software; you can redistribute it and/or
   modify it under the terms of the GNU Lesser General Public
   License as published by the Free Software Foundation; either
   version 3 of the License, or (at your option) any later version.

   This library 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
   Lesser General Public License for more details.

   You should have received a copy of the GNU Lesser General
   Public License along with this library; if not, write to the
   Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
   Boston, MA 02110-1301 USA */

#ifdef HAVE_CONFIG_H
# include <config.h>
#endif  
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#include <sieve-priv.h>

mu_sieve_machine_t mu_sieve_machine;
int mu_sieve_error_count;

static void branch_fixup (size_t start, size_t end);
static int _sieve_default_error_printer (void *, const char *, va_list);
static int _sieve_default_parse_error (void *, const char *, int,
				       const char *, va_list);
%}

%union {
  char *string;
  size_t number;
  sieve_instr_t instr;
  mu_sieve_value_t *value;
  mu_list_t list;
  size_t pc;
  struct {
    size_t start;
    size_t end;
  } pclist;
  struct {
    char *ident;
    mu_list_t args;
  } command;
  struct {
    size_t begin;
    size_t cond;
    size_t branch;
  } branch;
}

%token <string> IDENT TAG
%token <number> NUMBER
%token <string> STRING MULTILINE
%token REQUIRE IF ELSIF ELSE ANYOF ALLOF NOT

%type <value> arg
%type <list> slist stringlist stringorlist arglist maybe_arglist
%type <command> command
%type <pclist> testlist
%type <pc> action test statement list elsif else cond begin if block
%type <branch> elsif_branch maybe_elsif else_part

%%

input        : /* empty */
             | list
               { /* to placate bison */ }
             ;

list         : statement
             | list statement
             ;

statement    : REQUIRE stringorlist ';'
               {
		 mu_sieve_require ($2);
		 /*  All the items in $2 are registered in memory_pool,
		     so we don't free them */
		 mu_list_destroy (&$2);
		 $$ = mu_sieve_machine->pc;
	       }
             | action ';'
	     /* 1  2     3       4    */ 
             | if cond block else_part
               {
		 mu_sieve_machine->prog[$2].pc = $4.begin - $2 - 1;
		 if ($4.branch)
		   branch_fixup ($4.branch, mu_sieve_machine->pc);
	       }		 
             ;

if           : IF
               {
		 $$ = mu_sieve_machine->pc;
	       }
             ;

else_part    : maybe_elsif
               {
		 if ($1.begin)
		   mu_sieve_machine->prog[$1.cond].pc =
		                  mu_sieve_machine->pc - $1.cond - 1;
		 else
		   {
		     $$.begin = mu_sieve_machine->pc;
		     $$.branch = 0;
		   }
	       }
             | maybe_elsif else block
               {
		 if ($1.begin)
		   {
		     mu_sieve_machine->prog[$1.cond].pc = $3 - $1.cond - 1;
		     mu_sieve_machine->prog[$2].pc = $1.branch;
		     $$.begin = $1.begin;
		     $$.branch = $2;
		   }
		 else
		   {
		     $$.begin = $3;
		     $$.branch = $2;
		   }
	       }
             ;

maybe_elsif  : /* empty */
               {
		 $$.begin = 0;
	       }
             | elsif_branch
             ;

elsif_branch : elsif begin cond block
               {
		 $$.begin = $2; 
		 $$.branch = $1;
		 $$.cond = $3;
	       }
             | elsif_branch elsif begin cond block
               {
		 mu_sieve_machine->prog[$1.cond].pc = $3 - $1.cond - 1;
		 mu_sieve_machine->prog[$2].pc = $1.branch;
		 $$.begin = $1.begin;
		 $$.branch = $2;
		 $$.cond = $4;
	       }
             ;

elsif        : ELSIF
               {
		 mu_sv_code_instr (_mu_sv_instr_branch);
		 $$ = mu_sieve_machine->pc;
		 mu_sv_code_number (0);
	       }
             ;

else         : ELSE
               {
		 mu_sv_code_instr (_mu_sv_instr_branch);
		 $$ = mu_sieve_machine->pc;
		 mu_sv_code_number (0);
	       }
             ;

block        : '{' list '}'
               {
		 $$ = $2;
	       }
             ;

testlist     : cond_expr
               {
		 $$.start = $$.end = mu_sieve_machine->pc;
		 if (mu_sv_code_instr (_mu_sv_instr_brz)
		     || mu_sv_code_number (0))
		   YYERROR;
	       }
             | testlist ',' cond_expr
               {
		 mu_sieve_machine->prog[$1.end+1].pc = mu_sieve_machine->pc;
		 $1.end = mu_sieve_machine->pc;
		 if (mu_sv_code_instr (_mu_sv_instr_brz)
		     || mu_sv_code_number (0))
		   YYERROR;
		 $$ = $1;
	       }
             ;

cond         : cond_expr
               {
		 mu_sv_code_instr (_mu_sv_instr_brz);
		 $$ = mu_sieve_machine->pc;
		 mu_sv_code_number (0);
	       }
             ;

cond_expr    : test
               { /* to placate bison */ }
             | ANYOF '(' testlist ')'
               {
		 mu_sv_code_anyof ($3.start);
	       }
             | ALLOF '(' testlist ')'
               {
		 mu_sv_code_allof ($3.start);
	       }
             | NOT cond_expr
               {
		 if (mu_sv_code_instr (_mu_sv_instr_not))
		   YYERROR;
	       }
             ;

begin        : /* empty */
               {
		 $$ = mu_sieve_machine->pc;
	       }
             ; 

test         : command
               {
		 mu_sieve_register_t *reg = 
		        mu_sieve_test_lookup (mu_sieve_machine, $1.ident);
		 $$ = mu_sieve_machine->pc;

		 if (!reg)
		   mu_sv_compile_error (&mu_sieve_locus,
					_("unknown test: %s"),
					$1.ident);
		 else if (!reg->required)
		   mu_sv_compile_error (&mu_sieve_locus,
					_("test `%s' has not been required"),
					$1.ident);
		 else if (mu_sv_code_test (reg, $1.args))
		   YYERROR;
	       }
             ;

command      : IDENT maybe_arglist
               {
		 $$.ident = $1;
		 $$.args = $2;
	       }
             ;

action       : command
               {
		 mu_sieve_register_t *reg = 
		        mu_sieve_action_lookup (mu_sieve_machine, $1.ident);
		 
		 $$ = mu_sieve_machine->pc;
		 if (!reg)
		   mu_sv_compile_error (&mu_sieve_locus,
					_("unknown action: %s"),
					$1.ident);
		 else if (!reg->required)
		   mu_sv_compile_error (&mu_sieve_locus,
					_("action `%s' has not been required"),
					$1.ident);
		 else if (mu_sv_code_action (reg, $1.args))
		   YYERROR;
	       }
             ;

maybe_arglist: /* empty */
               {
		 $$ = NULL;
	       }
             | arglist
	     ;

arglist      : arg
               {
		 mu_list_create (&$$);
		 mu_list_append ($$, $1);
	       }		 
             | arglist arg
               {
		 mu_list_append ($1, $2);
		 $$ = $1;
	       }
             ;

arg          : stringlist
               {
		 $$ = mu_sieve_value_create (SVT_STRING_LIST, $1);
	       }
             | STRING
               {
		 $$ = mu_sieve_value_create (SVT_STRING, $1);
               } 
             | MULTILINE
               {
		 $$ = mu_sieve_value_create (SVT_STRING, $1);
	       }
             | NUMBER
               {
		 $$ = mu_sieve_value_create (SVT_NUMBER, &$1);
	       }
             | TAG
               {
		 $$ = mu_sieve_value_create (SVT_TAG, $1);
	       }
             ;

stringorlist : STRING
               {
		 mu_list_create (&$$);
		 mu_list_append ($$, $1);
	       }
             | stringlist
             ;

stringlist   : '[' slist ']'
               {
		 $$ = $2;
	       }
             ;

slist        : STRING
               {
		 mu_list_create (&$$);
		 mu_list_append ($$, $1);
	       }
             | slist ',' STRING
               {
		 mu_list_append ($1, $3);
		 $$ = $1;
	       }
             ;

%%

int
yyerror (const char *s)
{
  mu_sv_compile_error (&mu_sieve_locus, "%s", s);
  return 0;
}

int
mu_sieve_machine_init (mu_sieve_machine_t *pmach, void *data)
{
  int rc;
  mu_sieve_machine_t mach;
  size_t level;
  
  mach = malloc (sizeof (*mach));
  if (!mach)
    return ENOMEM;
  memset (mach, 0, sizeof (*mach));
  rc = mu_list_create (&mach->memory_pool);
  if (rc)
    {
      free (mach);
      return rc;
    }
  
  mach->data = data;
  mach->error_printer = _sieve_default_error_printer;
  mach->parse_error_printer = _sieve_default_parse_error;

  level = mu_global_debug_level ("sieve");
  if (level)
    {
      mu_debug_create (&mach->debug, mach);
      mu_debug_set_level (mach->debug, level);
    }
  
  *pmach = mach;
  return 0;
}

int
mu_sieve_machine_inherit (mu_sieve_machine_t const parent,
			  mu_sieve_machine_t *pmach)
{
  mu_sieve_machine_t child;
  int rc;
  
  rc = mu_sieve_machine_init (&child, parent->data);
  if (rc)
    return rc;
  child->logger = parent->logger;
  child->debug = parent->debug;
  child->debug_level = parent->debug_level;
  child->debug_printer = parent->debug_printer;
  *pmach = child;
  return 0;
}

int
mu_sieve_machine_dup (mu_sieve_machine_t const in, mu_sieve_machine_t *out)
{
  int rc;
  mu_sieve_machine_t mach;
  
  mach = malloc (sizeof (*mach));
  if (!mach)
    return ENOMEM;
  memset (mach, 0, sizeof (*mach));
  rc = mu_list_create (&mach->memory_pool);
  if (rc)
    {
      free (mach);
      return rc;
    }
  mach->destr_list = NULL;
  mach->test_list = NULL;
  mach->action_list = NULL;
  mach->comp_list = NULL;

  mach->progsize = in->progsize;
  mach->prog = in->prog;

  mach->pc = 0;
  mach->reg = 0;
  mach->stack = NULL;

  mach->debug_level = in->debug_level;
  
  mach->data = in->data;
  mach->error_printer = in->error_printer;
  mach->parse_error_printer = in->parse_error_printer;
  mach->debug_printer = in->debug_printer;
  mach->logger = in->logger;
  mach->debug = in->debug;
  mach->daemon_email = in->daemon_email;

  *out = mach;
  return 0;
}

void
mu_sieve_set_error (mu_sieve_machine_t mach, mu_sieve_printf_t error_printer)
{
  mach->error_printer = error_printer ?
                           error_printer : _sieve_default_error_printer;
}

void
mu_sieve_set_parse_error (mu_sieve_machine_t mach, mu_sieve_parse_error_t p)
{
  mach->parse_error_printer = p ? p : _sieve_default_parse_error;
}

void
mu_sieve_set_debug (mu_sieve_machine_t mach, mu_sieve_printf_t debug)
{
  mach->debug_printer = debug;
}

void
mu_sieve_set_debug_object (mu_sieve_machine_t mach, mu_debug_t dbg)
{
  mach->debug = dbg;
}

void
mu_sieve_set_debug_level (mu_sieve_machine_t mach, int level)
{
  mach->debug_level = level;
}

void
mu_sieve_set_logger (mu_sieve_machine_t mach, mu_sieve_action_log_t logger)
{
  mach->logger = logger;
}

mu_mailer_t
mu_sieve_get_mailer (mu_sieve_machine_t mach)
{
  if (!mach->mailer)
    {
      mu_mailer_create (&mach->mailer, NULL);
      if (mach->debug)
	mu_mailer_set_debug (mach->mailer, mach->debug);
    }

  return mach->mailer;
}

void
mu_sieve_set_mailer (mu_sieve_machine_t mach, mu_mailer_t mailer)
{
  mu_mailer_destroy (&mach->mailer);
  mach->mailer = mailer;
}

#define MAILER_DAEMON_PFX "MAILER-DAEMON@"

char *
mu_sieve_get_daemon_email (mu_sieve_machine_t mach)
{
  if (!mach->daemon_email)
    {
      const char *domain = NULL;
      
      mu_get_user_email_domain (&domain);
      mach->daemon_email = mu_sieve_malloc (mach,
					 sizeof(MAILER_DAEMON_PFX) +
					 strlen (domain));
      sprintf (mach->daemon_email, "%s%s", MAILER_DAEMON_PFX, domain);
    }
  return mach->daemon_email;
}

void
mu_sieve_set_daemon_email (mu_sieve_machine_t mach, const char *email)
{
  mu_sieve_mfree (mach, (void *)mach->daemon_email);
  mach->daemon_email = mu_sieve_mstrdup (mach, email);
}

struct sieve_destr_record
{
  mu_sieve_destructor_t destr;
  void *ptr;
};

int
mu_sieve_machine_add_destructor (mu_sieve_machine_t mach,
				 mu_sieve_destructor_t destr,
				 void *ptr)
{
  struct sieve_destr_record *p;

  if (!mach->destr_list && mu_list_create (&mach->destr_list))
    return 1;
  p = mu_sieve_malloc (mach, sizeof (*p));
  if (!p)
    return 1;
  p->destr = destr;
  p->ptr = ptr;
  return mu_list_prepend (mach->destr_list, p);
}

static int
_run_destructor (void *data, void *unused)
{
  struct sieve_destr_record *p = data;
  p->destr (p->ptr);
  return 0;
}

void
mu_sieve_machine_destroy (mu_sieve_machine_t *pmach)
{
  mu_sieve_machine_t mach = *pmach;
  mu_mailer_destroy (&mach->mailer);
  mu_list_do (mach->destr_list, _run_destructor, NULL);
  mu_list_destroy (&mach->destr_list);
  mu_list_destroy (&mach->action_list);
  mu_list_destroy (&mach->test_list);
  mu_list_destroy (&mach->comp_list);
  mu_list_destroy (&mach->source_list);
  mu_sieve_slist_destroy (&mach->memory_pool);
  free (mach);
  *pmach = NULL;
}

static int
string_comp (const void *item, const void *value)
{
  return strcmp (item, value);
}

void
mu_sieve_machine_begin (mu_sieve_machine_t mach, const char *file)
{
  mu_sieve_machine = mach;
  mu_sieve_error_count = 0;
  mu_sv_code_instr (NULL);

  mu_list_create (&mach->source_list);
  mu_list_set_comparator (mach->source_list, string_comp);
  
  mu_sv_register_standard_actions (mach);
  mu_sv_register_standard_tests (mach);
  mu_sv_register_standard_comparators (mach);
}

void
mu_sieve_machine_finish (mu_sieve_machine_t mach)
{
  mu_sv_code_instr (NULL);
}

int
mu_sieve_compile (mu_sieve_machine_t mach, const char *name)
{
  int rc;
  
  mu_sieve_machine_begin (mach, name);

  if (mu_sv_lex_begin (name) == 0)
    {
      rc = yyparse ();
      if (mu_sieve_error_count)
	rc = 1;
      mu_sv_lex_finish ();
    }
  else
    rc = 1;
  
  mu_sieve_machine_finish (mach);
  return rc;
}

int
mu_sieve_compile_buffer (mu_sieve_machine_t mach,
			 const char *buf, int bufsize,
			 const char *fname, int line)
{
  int rc;
  
  mu_sieve_machine_begin (mach, fname);

  if (mu_sv_lex_begin_string (buf, bufsize, fname, line) == 0)
    {
      rc = yyparse ();
      if (mu_sieve_error_count)
	rc = 1;
      mu_sv_lex_finish ();
    }
  else
    rc = 1;
  
  mu_sieve_machine_finish (mach);
  return rc;
}

static void
_branch_fixup (size_t start, size_t end)
{
  size_t prev = mu_sieve_machine->prog[start].pc;
  if (!prev)
    return;
  branch_fixup (prev, end);
  mu_sieve_machine->prog[prev].pc = end - prev - 1;
}

static void
branch_fixup (size_t start, size_t end)
{
  _branch_fixup (start, end);
  mu_sieve_machine->prog[start].pc = end - start - 1;
}

static int
_sieve_default_error_printer (void *unused, const char *fmt, va_list ap)
{
  return mu_verror (fmt, ap);
}

static int
_sieve_default_parse_error (void *unused, const char *filename, int lineno,
			    const char *fmt, va_list ap)
{
  mu_debug_t debug;

  mu_diag_get_debug (&debug);
  if (filename)
    mu_debug_set_locus (debug, filename, lineno);
  mu_diag_vprintf (MU_DIAG_ERROR, fmt, ap);
  mu_diag_printf (MU_DIAG_ERROR, "\n");
  mu_debug_set_locus (debug, NULL, 0);
  return 0;
}