%{
/*
**  OSSP l2 - Flexible Logging
**  Copyright (c) 2001-2003 Cable & Wireless Deutschland GmbH
**  Copyright (c) 2001-2003 The OSSP Project (http://www.ossp.org/)
**  Copyright (c) 2001-2003 Ralf S. Engelschall <rse@engelschall.com>
**
**  This file is part of OSSP l2, a flexible logging library which
**  can be found at http://www.ossp.org/pkg/lib/l2/.
**
**  Permission to use, copy, modify, and distribute this software for
**  any purpose with or without fee is hereby granted, provided that
**  the above copyright notice and this permission notice appear in all
**  copies.
**
**  THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
**  WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
**  MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
**  IN NO EVENT SHALL THE AUTHORS AND COPYRIGHT HOLDERS AND THEIR
**  CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
**  SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
**  LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
**  USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
**  ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
**  OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
**  OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
**  SUCH DAMAGE.
**
**  l2_spec_parse.y: GNU Bison (Yacc-style) parser specification
**
**  ATTENTION: This requires GNU Bison 1.875 or newer!
*/

#include "l2.h"      /* for l2_xxx() API */
#include "l2_spec.h" /* for l2_spec_ctx_t */

/* make sure yyparse() accepts a context pointer and
   passes through its inlined scanner context to yylex() */
#define CTX           ((l2_spec_ctx_t *)ctx)
#define YYPARSE_PARAM ctx
#define YYLEX_PARAM   CTX->yyscan

/* provide an explicit prototype for the yylex() function but use
   "void *" instead of correct type because at this point in the
   generation process we have the types still not available */
extern int yylex(/*YYSTYPE*/ void *lvalp,
                 /*YYLTYPE*/ void *llocp, l2_spec_ctx_t *ctx);

/* generate verbose error messages and remember them inside the context */
#undef  yyerror
#define yyerror(msg) \
    l2_spec_error(CTX, L2_ERR_SYN, &yylloc, msg)

/* scanner state transition */
extern void l2_spec_scan_push(l2_spec_ctx_t *, const char *state);
extern void l2_spec_scan_pop(l2_spec_ctx_t *);
%}

/* parser options */
%error-verbose
%pure_parser
%locations
%defines

/* the YYSTYPE: parser token value */
%union {
    char         *cpValue;
    l2_channel_t *chChannel;
    unsigned int  nLevels;
}

/* assign YYSTYPE elements to parser rules */
%type <chChannel> stream
%type <chChannel> streams
%type <chChannel> channel
%type <chChannel> channel_cons
%type <nLevels>   channel_level
%type <nLevels>   channel_level_mask

/* list of scanner tokens */
%token <cpValue>  T_ID
%token <cpValue>  T_PARAM
%token <cpValue>  T_STRING
%token            T_OP_ARROW

/* operator association */
%right T_OP_ARROW

/* grammar start rule
   (technically redundant but explicit to be sure) */
%start tree

%%

/* channel tree */
tree
    : stream {
          CTX->ch = $1;
      }
    ;

/* stream of channels */
stream
    : /* empty */ {
          l2_channel_t *ch;
          if ((CTX->rv = l2_channel_create(&ch, CTX->env, "null")) != L2_OK) {
              l2_spec_error(CTX, CTX->rv, &yylloc, "failed to create channel 'null'");
              YYERROR;
          }
          $$ = ch;
      }
    | channel {
          $$ = $1;
      }
    | channel T_OP_ARROW stream {
          $$ = $1;
          if ((CTX->rv = l2_channel_link($1, L2_LINK_CHILD, $3, NULL)) != L2_OK) {
              l2_spec_error(CTX, CTX->rv, &yylloc, "unable to link parent with child");
              YYERROR;
          }
      }
    | channel T_OP_ARROW '{' streams '}' {
          $$ = $1;
          if ((CTX->rv = l2_channel_link($1, L2_LINK_CHILD, $4, NULL)) != L2_OK) {
              l2_spec_error(CTX, CTX->rv, &yylloc, "unable to link parent with child list");
              YYERROR;
          }
      }
    ;

/* list of sibling streams */
streams
    : stream {
          $$ = $1;
      }
    | stream ';' streams {
          $$ = $1;
          if ((CTX->rv = l2_channel_link($1, L2_LINK_SIBLING, $3, NULL)) != L2_OK) {
              l2_spec_error(CTX, CTX->rv, &yylloc, "unable to link childs together");
              YYERROR;
          }
      }
    ;

/* channel */
channel
    : channel_level '/' channel_level ':' channel_cons {
          $$ = $5;
          if ((CTX->rv = l2_channel_levels($5, $1, $3)) != L2_OK) {
              l2_spec_error(CTX, CTX->rv, &yylloc, "failed to set channel write and flush levels");
              YYERROR;
          }
      }
    | channel_level ':' channel_cons {
          $$ = $3;
          if ((CTX->rv = l2_channel_levels($3, $1, L2_LEVEL_NONE)) != L2_OK) {
              l2_spec_error(CTX, CTX->rv, &yylloc, "failed to set channel write levels");
              YYERROR;
          }
      }
    | channel_cons {
          $$ = $1;
      }
    ;

/* channel level */
channel_level
    : T_ID {
          unsigned int levelmask;
          if ((CTX->rv = l2_util_s2l($1, strlen($1), ',', &levelmask)) != L2_OK) {
              l2_spec_error(CTX, CTX->rv, &yylloc, "invalid level '%s'", $1);
              free($1);
              YYERROR;
          }
          free($1);
          $$ = L2_LEVEL_UPTO(levelmask);
      }
    | '(' channel_level_mask ')' {
          $$ = $2;
      }
    ;

/* channel level mask */
channel_level_mask
    : T_ID {
          unsigned int levelmask;
          if ((CTX->rv = l2_util_s2l($1, strlen($1), ',', &levelmask)) != L2_OK) {
              l2_spec_error(CTX, CTX->rv, &yylloc, "invalid level '%s'", $1);
              free($1);
              YYERROR;
          }
          free($1);
          $$ = levelmask;
      }
    | T_ID '|' channel_level_mask {
          unsigned int levelmask;
          if ((CTX->rv = l2_util_s2l($1, strlen($1), ',', &levelmask)) != L2_OK) {
              l2_spec_error(CTX, CTX->rv, &yylloc, "invalid level '%s'", $1);
              free($1);
              YYERROR;
          }
          free($1);
          $$ = levelmask | $3;
      }
    ;

/* channel constructor */
channel_cons
    : T_ID {
          l2_channel_t *ch;
          if ((CTX->rv = l2_channel_create(&ch, CTX->env, $1)) != L2_OK) {
              l2_spec_error(CTX, CTX->rv, &yylloc, "failed to create channel '%s'", $1);
              free($1);
              YYERROR;
          }
          free($1);
          $$ = ch;
          /* provide channel to channel_param rule below because it does
             not know where on the token stack our $$ is because it is a
             sub-rule of the recursive channel_param_list rule and hence
             cannot use "$<chChannel>-n". */
          CTX->chTmp = ch;
      }
      channel_params {
          $$ = $<chChannel>2; /* pass-through result */
          CTX->chTmp = NULL;
      }
    ;

/* channel parameters */
channel_params
    : /* empty */
    | '(' ')'
    | '(' channel_param_list ')'
    ;

/* channel parameter list */
channel_param_list
    : channel_param
    | channel_param ',' channel_param_list
    ;

/* channel parameter */
channel_param
    : T_ID '=' { l2_spec_scan_push(CTX, "SS_PARAM"); } T_PARAM { l2_spec_scan_pop(CTX); } {
          if ((CTX->rv = l2_channel_configure(CTX->chTmp, "%s=\"%s\"", $1, $4)) != L2_OK) {
              l2_spec_error(CTX, CTX->rv, &yylloc, "failed to configure channel with '%s=\"%s\"'", $1, $4);
              free($1);
              free($4);
              YYERROR;
          }
          free($1);
          free($4);
      }
    ;

%%

