/* -*- c -*-

  elmo - ELectronic Mail Operator

  Copyright (C) 2003, 2004 rzyjontko

  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; version 2.

  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.  

  ------------------------------------------------------------

*/

%{

#define _GNU_SOURCE 1

/****************************************************************************
 *    IMPLEMENTATION HEADERS
 ****************************************************************************/

#ifdef HAVE_CONFIG_H
# include <config.h>
#endif

#include <stdio.h>
#include <errno.h>
#include <string.h>
#include <ctype.h>
#include <sys/stat.h>

#ifdef HAVE_WORDEXP_H
# include <wordexp.h>
#endif


#include "error.h"
#include "xmalloc.h"
#include "confhold.h"
#include "misc.h"
#include "ecurses.h"
#include "keymap.h"
#include "cmd.h"
#include "gettext.h"
#include "abook.h"
#include "procmail.h"
#include "mime.h"
#include "property.h"

/****************************************************************************
 *    IMPLEMENTATION PRIVATE DEFINITIONS / ENUMERATIONS / SIMPLE TYPEDEFS
 ****************************************************************************/

#define FIRST_ALLOC 128
#define MAX_DEPTH   10

#define YY_DECL static token_t next_token YY_PROTO ((void))

        typedef enum token {KW_SET       = 300,
                            KW_HOOK      = 301,
                            KW_KEY       = 302,
                            KW_ADDR      = 303,
                            KW_INCLUDE   = 304,
                            KW_RULE      = 305,
                            KW_TRANSLATE = 306,
                            KW_MIME      = 307,
                            KW_HANDLER   = 308,
                            KW_PROPERTY  = 309,
                            KW_MACRO     = 310,

                            IDENTIFIER   = 401,
                            EOL          = 402,
                            SQUOTED      = 403,
                            DQUOTED      = 404,
                            TEXT         = 405,
                            ARROW        = 406,

                            KEY_SPECIAL  = 501,
                            KEY_CONTROL  = 502,
                            KEY_SIMPLE   = 503,
                            KEY_VALUE    = 504,
        } token_t;

/****************************************************************************
 *    IMPLEMENTATION PRIVATE CLASS PROTOTYPES / EXTERNAL CLASS REFERENCES
 ****************************************************************************/
/****************************************************************************
 *    IMPLEMENTATION PRIVATE STRUCTURES / UTILITY CLASSES
 ****************************************************************************/

        typedef struct finfo {
                YY_BUFFER_STATE  state;
                char            *fname;
                int              lineno;
        } finfo_t;

/****************************************************************************
 *    IMPLEMENTATION REQUIRED EXTERNAL REFERENCES (AVOID)
 ****************************************************************************/
/****************************************************************************
 *    IMPLEMENTATION PRIVATE DATA
 ****************************************************************************/

        static token_t current_token = 0;

        /**
         * this is used for recursive file inclusion
         *
         * fstack and ftop are to be used only by frame_pop, and frame_push
         * others should use these functions and get data from fframe
         *
         * ftop points to the top used frame, so that fstack[ftop + 1] is the
         * first available
         */
        static finfo_t  fstack[MAX_DEPTH];
        static int      ftop = -1;
        static finfo_t *fframe;

        /**
         * these are copies of yytext and yyleng
         */
        static char *token_txt = NULL;
        static int   token_len = 0;

        /**
         * key-value, and a flag if a key is a meta-key
         */
        static int token_key = 0;
        static int key_meta  = 0;

        /**
         * this is set to 0 if confhold does not have appropriate structures
         * to hold new variable, it may happen if someone defines not registered
         * variable
         */
        static int prepared_to_insert = 1;

        /**
         * this holds field's name that is passed to confhold_insert_value
         */
        static char *field_name = NULL;

/****************************************************************************
 *    INTERFACE DATA
 ****************************************************************************/
/****************************************************************************
 *    IMPLEMENTATION PRIVATE FUNCTION PROTOTYPES
 ****************************************************************************/

        static int parse (void);
        static int frame_push (char *file, int silent);
        static int frame_pop (void);

/****************************************************************************
 *    INTERFACE FUNCTIONS
 ****************************************************************************/

%}

%option noyywrap


ID [a-zA-Z_][a-zA-Z0-9_\-]+
SQT \'[^\']*\'
DQT \"[^\"]*\"
SKEY1 (<tab>)|(<esc>)|(<up>)|(<down>)|(<left>)|(<right>)|(<pageup>)
SKEY2 (<pagedown>)|(<backspace>)|(<delete>)|(<insert>)|(<enter>)|(<home>)
SKEY3 (<end>)|(<space>)|(<f[0-9]{1,2}>)
KEY ([^<>\n \t:\\\{\}\'\"\(\)])|(\\[^CM0-9\n])
VKEY \\[0-9]{3}

%%

^[ \t]*#.* /* ignore */

[ \t]      /* ignore */

^set       return KW_SET;

^key       return KW_KEY;

^hook      return KW_HOOK;

^addr      return KW_ADDR;

^include   return KW_INCLUDE;

^rule      return KW_RULE;

^translate return KW_TRANSLATE;

^mime      return KW_MIME;

^handler   return KW_HANDLER;

^property  return KW_PROPERTY;

^macro     return KW_MACRO;

{ID}       return IDENTIFIER;

{SQT}      return SQUOTED;

{DQT}      return DQUOTED;

"=>"       return ARROW;

(\\M)?({KEY}) {
        char *seek;

        if (strstr (yytext, "\\M") == yytext){
                key_meta = 1;
                seek     = yytext + 2;
        }
        else {
                key_meta = 0;
                seek     = yytext;
        }

        if (*seek == '\\')
                token_key = seek[1];
        else
                token_key = seek[0];

        return KEY_SIMPLE;
}

(\\M)?({SKEY1}|{SKEY2}|{SKEY3}) {
        int   f;
        char *seek;
  
        if (strstr (yytext, "\\M") == yytext){
                key_meta = 1;
                seek     = yytext + 2;
        }
        else {
                key_meta = 0;
                seek     = yytext;
        }

        if (! strcmp (seek, "<tab>"))
                token_key = 9;
        else if (! strcmp (seek, "<esc>"))
                token_key = 27;
        else if (! strcmp (seek, "<up>"))
                token_key = KEY_UP;
        else if (! strcmp (seek, "<down>"))
                token_key = KEY_DOWN;
        else if (! strcmp (seek, "<left>"))
                token_key = KEY_LEFT;
        else if (! strcmp (seek, "<right>"))
                token_key = KEY_RIGHT;
        else if (! strcmp (seek, "<pageup>"))
                token_key = KEY_PPAGE;
        else if (! strcmp (seek, "<pagedown>"))
                token_key = KEY_NPAGE;
        else if (! strcmp (seek, "<backspace>"))
                token_key = KEY_BACKSPACE;
        else if (! strcmp (seek, "<delete>"))
                token_key = KEY_DC;
        else if (! strcmp (seek, "<insert>"))
                token_key = KEY_IC;
        else if (! strcmp (seek, "<enter>"))
                token_key = '\r';
        else if (! strcmp (seek, "<home>"))
                token_key = KEY_HOME;
        else if (! strcmp (seek, "<end>"))
                token_key = KEY_END;
        else if (! strcmp (seek, "<space>"))
                token_key = ' ';
        else {
                f = atoi (seek + 2);
                token_key = KEY_F (f);
        }

        return KEY_SPECIAL;
}

(\\M)?({VKEY}) {
        char *seek;
        
        if (strstr (yytext, "\\M") == yytext){
                key_meta = 1;
                seek     = yytext + 2;
        }
        else {
                key_meta = 0;
                seek     = yytext;
        }

        token_key = strtol (seek + 1, NULL, 8);

        return KEY_VALUE;
}

(\\M)?(\\C[a-z]) {
        char *seek;

        if (strstr (yytext, "\\M") == yytext){
                key_meta = 1;
                seek     = yytext + 2;
        }
        else {
                key_meta = 0;
                seek     = yytext;
        }

        token_key = seek[2] - 'a' + 1;

        return KEY_CONTROL;
}

\\\n  fframe->lineno++;

\n    fframe->lineno++; return EOL;

[^ \t\n:\\<>\{\}\(\),]+ return TEXT;

.     return *yytext;

<<EOF>> {
        if (ftop > 0)
                frame_pop ();
        else {
                return EOF;
                yyunput (0, NULL);
        }
}

%%


int
confread_read_file (const char *file, int silent)
{
        int ret;

        ftop = -1;
  
        if (frame_push (xstrdup (file), silent))
                return -1;
        current_token  = next_token ();
        ret            = parse ();
        frame_pop ();
  
        if (token_txt)
                xfree (token_txt);
        token_txt = NULL;
  
        return ret;
}


/****************************************************************************
 *    IMPLEMENTATION PRIVATE FUNCTIONS
 ****************************************************************************/


static char *
expand (char *str)
{
#ifdef HAVE_WORDEXP_H
        wordexp_t  word_vector;
        int        len = 0;
        int        i;
        char      *result;
        char      *end;

        if (wordexp (str, &word_vector, 0)){
                return NULL;
        }

        for (i = 0; i < word_vector.we_wordc; i++){
                len += strlen (word_vector.we_wordv[i]);
        }

        result = xmalloc (len + 1);
        end    = result;
        for (i = 0; i < word_vector.we_wordc; i++){
                end  = stpcpy (end, word_vector.we_wordv[i]);
        }
        *end   = '\0';
        wordfree (&word_vector);

        return result;
#else
        int   len = strlen (str);
        char *result;

        if ((*str == '"' && str[len - 1] == '"')
            || (*str == '\'' && str[len - 1] == '\'')){
                result = xmalloc (len - 2 + 1);
                memcpy (result, str + 1, len - 2);
                result[len - 2] = '\0';
        }

        return xstrdup (str);
#endif
}



static cmd_state_t
name_to_state (char *name)
{
        if (! strcmp (name, "folder"))
                return CMD_LIST;
        if (! strcmp (name, "mail"))
                return CMD_READ_MAIL;
        if (! strcmp (name, "select"))
                return CMD_SELECT_BOX;
        if (! strcmp (name, "fetch"))
                return CMD_FETCH;
        if (! strcmp (name, "send"))
                return CMD_SENDER;
        if (! strcmp (name, "attach"))
                return CMD_ATTACH;
        if (! strcmp (name, "abook"))
                return CMD_ABOOK;
        if (! strcmp (name, "abook_add"))
                return CMD_ABOOK_ADD;
        if (! strcmp (name, "ask"))
                return CMD_ASK;
        if (! strcmp (name, "help"))
                return CMD_HELP;
        if (! strcmp (name, "read"))
                return CMD_READ;
        if (! strcmp (name, "debug"))
                return CMD_DEBUG;
        if (! strcmp (name, "search"))
                return CMD_SEARCH;
        return CMD_INVALID;
}



static int
frame_push (char *fname, int silent)
{
        FILE        *fp;
        struct stat  st;
  
        if (ftop >= MAX_DEPTH){
                error_ (0, _("includes nested to deep"));
                xfree (fname);
                return 1;
        }
  
        if (stat (fname, & st)){
                if (! silent)
                        error_ (errno, "%s", fname);
                xfree (fname);
                return 1;
        }

        if (! S_ISREG (st.st_mode)){
                error_ (0, _("%s is not a file"), fname);
                xfree (fname);
                return 1;
        }
        
        fp  = fopen (fname, "r");
        if (fp == NULL){
                error_ (errno, "%s", fname);
                xfree (fname);
                return 1;
        }

        ftop++;
  
        yyin           = fp;
        fframe         = fstack + ftop;
        fframe->fname  = fname;
        fframe->lineno = 1;
        fframe->state  = yy_create_buffer (yyin, YY_BUF_SIZE);

        yy_switch_to_buffer (fframe->state);

        return 0;
}



static int
frame_pop (void)
{
        xfree (fframe->fname);
        yy_delete_buffer (fframe->state);
        fclose (yyin);

        if (ftop > 0){
                ftop--;
                fframe = fstack + ftop;

                yy_switch_to_buffer (fframe->state);
        }
        else {
                fframe = NULL;
        }

        return 0;
}


/****************************************************************************
 *    PARSER FUNCTIONS
 ****************************************************************************/

/**
 * Please note, that there is no error-recovery.  First error generates
 * parse error message and stops file analysis.
 */

/*
  GRAMMAR

  <command-list>  ::= <command-list> <command> | <command>
  <command>       ::= <hook-command> | <key-command> | <set-command>
                    | <addr-command> | <include> | <rule-command>
                    | <translate-cmd> | <mime-command> | <handler-cmd>
                    | <property-cmd>
  <hook-command>  ::= "hook" ID ID EOL
  <key-command>   ::= "key" ID <key-specifier> ID EOL
  <set-command>   ::= "set" ID <simple-value> EOL
                    | "set" ID "{" EOL <struct-value> "}"
  <addr-command>  ::= "addr" "{" EOL <struct-value> "}"
  <rule-command>  ::= "rule" ID "{" <rule-content> "}"
  <translate-cmd> ::= "translate" <simple-value> <simple-value>
  <mime-command>  ::= "mime" <simple-value> <simple-value> EOL
  <handler-cmd>   ::= "handler" <simple-value> <simple-value> EOL
  <property-cmd>  ::= "property" <simple-value> "(" <property-args> ")" "{" 
                      EOL <property-body> "}"
  <include>       ::= "include" <simple-value> EOL
  <key-specifier> ::= KEY | CONTROL-KEY | SPECIAL | VALUE
  <struct-value>  ::= <struct-value> <field> | <field>
  <rule-content>  ::= <constraints> <rule-action>
  <constraints>   ::= <constraints> <rule-header> <rule-constraint> | ""
  <rule-header>   ::= <crap> <rule-header> | <real-header>
  <rule-constraint>::= <crap> <rule-constraint> | <real-constraint>
  <rule-action>   ::= <crap> <rule-action> | <real-action>
  <crap>          ::= ID | EOL | TEXT | ...
  <real-header>   ::= "TO" | "SUBJECT" | "FROM" | "CC" | "TOCC"
  <real-constraint>::= SQUOTED | DQUOTED
  <real-action>   ::= TEXT"."
  <property-args> ::= <property-args> <property-arg> | <property-arg>
  <property-arg>  ::= TEXT | IDENTIFIER | EOL
  <property-body> ::= <property-body> <property-rule> | <property-rule>
  <property-rule> ::= <property-conds> "=>" <simple-value>
  <property-conds>::= <property-conds> <property-cond> | <property-cond>
  <property-cond> ::= SQUOTED | DQUOTED | EOL
  <field>         ::= ID ":" <simple-value> EOL | EOL
  <simple-value>  ::= ID | SQUOTED | DQUOTED | TEXT
*/


static char *
token_2_string (int token)
{
        static char token_txt[2] = {'\0', '\0'};
  
        if (token > 0 && token < 255){
                *token_txt = token;
                return token_txt;
        }

        switch (token){

                case KW_SET:
                        return "set";

                case KW_KEY:
                        return "key";

                case KW_HOOK:
                        return "hook";

                case KW_ADDR:
                        return "addr";

                case KW_INCLUDE:
                        return "include";

                case KW_RULE:
                        return "rule";

                case KW_TRANSLATE:
                        return "translate";

                case KW_MIME:
                        return "mime";

                case KW_HANDLER:
                        return "handler";

                case KW_PROPERTY:
                        return "property";

                case KW_MACRO:
                        return "macro";

                case IDENTIFIER:
                        return _("identifier");

                case EOL:
                        /* it's tricky but this is about end of
                           PREVIOUS line */
                        fframe->lineno--;
                        return _("end of line");

                case TEXT:
                        return _("text");
                
                case SQUOTED:
                case DQUOTED:
                        return _("quoted text");

                case ARROW:
                        return "=>";

                case KEY_SPECIAL:
                case KEY_CONTROL:
                case KEY_SIMPLE:
                case KEY_VALUE:
                        return _("key specifier");

                case EOF:
                        return _("end of file");
        }

        return "";
}


static void
parse_error (void)
{
        error_ (0, _("%s:%d: parse error near %s"), fframe->fname,
                fframe->lineno, token_2_string (current_token));
}



static int
match (token_t token)
{
        token_t old_token = current_token;

        if (token_txt == NULL){
                token_len = FIRST_ALLOC + yyleng;
                token_txt = xmalloc (token_len + 1);
        }
        else if (token_len < yyleng + 2){
                token_len += yyleng + 2;
                xfree (token_txt);
                token_txt = xmalloc (token_len);
        }

        memcpy (token_txt, yytext, yyleng + 1);

        current_token = next_token ();
        return old_token != token;
}




static int
set_field_value (void)
{
        char *txt;
  
        switch (current_token){

                case IDENTIFIER:
                case KEY_SIMPLE:
                case TEXT:
                case SQUOTED:
                case DQUOTED:
                        match (current_token);
                        if (! prepared_to_insert)
                                return 0;
                        txt = expand (token_txt);
                        if (txt){
                                if (confhold_insert_value (field_name, txt))
                                        error_ (0, _("%s:%d: invalid field: %s"),
                                                fframe->fname, fframe->lineno,
                                                field_name);
                                xfree (txt);
                        }
                        return 0;

                default:
                        return 1;
        }
}



static int
set_field (void)
{
        if (current_token == EOL)
                return match (EOL);
  
        if (match (IDENTIFIER))
                return 1;

        field_name = xstrdup (token_txt);

        if (match (':')){
                if (field_name)
                        xfree (field_name);
                field_name = NULL;
                return 1;
        }

        if (set_field_value ()){
                if (field_name)
                        xfree (field_name);
                field_name = NULL;
                return 1;
        }

        xfree (field_name);
        field_name = NULL;

        return match (EOL);
}



static int
set_struct_value (void)
{
        do {
                if (set_field ())
                        return 1;
        }
        while (current_token == IDENTIFIER || current_token == EOL);

        return 0;
}



static int
set_value (void)
{
        char *txt;
  
        switch (current_token){

                case '{':
                        match ('{');
                        if (match (EOL))
                                return 1;
                        if (set_struct_value ())
                                return 1;
                        return match ('}');

                case IDENTIFIER:
                case SQUOTED:
                case DQUOTED:
                case TEXT:
                case KEY_SIMPLE:
                        match (current_token);
                        if (! prepared_to_insert)
                                return match (EOL);
                        txt = expand (token_txt);
                        if (txt){
                                confhold_insert_value (NULL, txt);
                                xfree (txt);
                        }
                        return match (EOL);
      
                default:
                        return 1;
        }
}



static int
set_command (void)
{
        prepared_to_insert = 1;
  
        if (match (KW_SET))
                return 1;

        if (match (IDENTIFIER))
                return 1;

        if (confhold_prepare_to_insert (token_txt)){
                prepared_to_insert = 0;
                error_ (0, _("%s:%d: no such variable: %s"), fframe->fname,
                        fframe->lineno, token_txt);
        }

        return set_value ();
}



static int
hook_command (void)
{
        exec_t *trigger;
        exec_t *action;
  
        if (match (KW_HOOK))
                return 1;

        if (match (IDENTIFIER))
                return 1;

        trigger = exec_lookup (token_txt);

        if (trigger == NULL){
                error_ (0, _("%s:%d: no such function: %s"), fframe->fname,
                        fframe->lineno, token_txt);
        }

        if (match (IDENTIFIER))
                return 1;

        action = exec_lookup (token_txt);

        if (action == NULL){
                error_ (0, _("%s:%d: no such function: %s"), fframe->fname,
                        fframe->lineno - 1, token_txt);
        }

        if (action->fun){
                if (trigger && action){
                        if (trigger->hook == NULL)
                                trigger->hook = hook_create (action->fun);
                        else
                                hook_add (trigger->hook, action->fun);
                }
        }
        else {
                error_ (0, _("%s:%d: macros (%s) may not be used as actions "
                             "in hook command"), fframe->fname,
                        fframe->lineno - 1, token_txt);
        }

        return match (EOL);
}



static int
key_specifier (void)
{
        switch (current_token){

                case KEY_SIMPLE:
                case KEY_VALUE:
                case KEY_SPECIAL:
                case KEY_CONTROL:
                        match (current_token);
                        return 0;

                default:
                        return 1;
        }
}


static int
key_command (void)
{
        cmd_state_t  state;
        exec_t      *exec;
  
        if (match (KW_KEY))
                return 1;

        if (match (IDENTIFIER))
                return 1;

        state = name_to_state (token_txt);

        if (state == CMD_INVALID)
                error_ (0, _("%s:%d: no such state: %s"), fframe->fname,
                        fframe->lineno, token_txt);
  
        if (key_specifier ())
                return 1;

        if (match (IDENTIFIER))
                return 1;

        exec = exec_lookup (token_txt);

        if (exec == NULL)
                error_ (0, _("%s:%d: no such function: %s"), fframe->fname,
                        fframe->lineno - 1, token_txt);

        if (state != CMD_INVALID && exec){
                keymap_add (keymaps + state, token_key, key_meta, exec->fun);
        }

        return match (EOL);
}



static int
addr_field_value (void)
{
        char *txt;
  
        switch (current_token){

                case IDENTIFIER:
                case TEXT:
                case SQUOTED:
                case DQUOTED:
                case KEY_SIMPLE:
                        match (current_token);
                        txt = expand (token_txt);
                        if (txt){
                                abook_new_value (txt);
                                xfree (txt);
                        }
                        return 0;

                default:
                        return 1;
        }
}



static int
addr_field (void)
{
        switch (current_token){

                case EOL:
                        match (EOL);
                        return 0;

                case IDENTIFIER:
                        match (IDENTIFIER);
                        if (abook_new_field (token_txt))
                                error_ (0, _("%s:%d: invalid field: %s"),
                                        fframe->fname, fframe->lineno,
                                        token_txt);
                        if (match (':'))
                                return 1;
                        return addr_field_value ();

                default:
                        return 1;
        }
}



static int
addr_value (void)
{
        do {
                if (addr_field ())
                        return 1;
        }
        while (current_token == IDENTIFIER || current_token == EOL);

        return 0;
}



static int
addr_command (void)
{
        if (match (KW_ADDR))
                return 1;

        if (match ('{'))
                return 1;

        if (match (EOL))
                return 1;

        abook_new_prepare ();

        if (addr_value ()){
                abook_new_drop ();
                return 1;
        }

        abook_new_store ();
  
        return match ('}');
}



static int
include_command (void)
{
        char *fname;
  
        if (match (KW_INCLUDE))
                return 1;

        switch (current_token){

                case DQUOTED:
                case SQUOTED:
                case IDENTIFIER:
                case TEXT:
                        match (current_token);
                        fname = expand (token_txt);
                        frame_push (fname, 0);
                        return match (EOL);

                default:
                        return 1;
        }
}



static int
rule_header (void)
{
        while (1){
                switch (current_token){

                        case IDENTIFIER:
                                match (IDENTIFIER);
                                if (strcmp (token_txt, "SUBJECT") == 0
                                    || strcmp (token_txt, "TO") == 0
                                    || strcmp (token_txt, "FROM") == 0
                                    || strcmp (token_txt, "CC") == 0
                                    || strcmp (token_txt, "TOCC") == 0){
                                        procmail_setup_header (token_txt);
                                        return 0;
                                }
                                break;

                        case EOL:
                        case TEXT:
                        case KEY_SPECIAL:
                        case KEY_CONTROL:
                        case KEY_SIMPLE:
                        case KEY_VALUE:
                                match (current_token);
                                break;

                        default:
                                return 1;
                }
        }
}



static int
rule_constraint (void)
{
        char *txt;
        
        while (1){
                switch (current_token){

                        case SQUOTED:
                                match (SQUOTED);
                                txt = expand (token_txt);
                                procmail_setup_str (txt);
                                xfree (txt);
                                return 0;

                        case DQUOTED:
                                match (DQUOTED);
                                txt = expand (token_txt);
                                procmail_setup_re (txt);
                                xfree (txt);
                                return 0;

                        case IDENTIFIER:
                        case EOL:
                        case TEXT:
                        case KEY_SPECIAL:
                        case KEY_CONTROL:
                        case KEY_SIMPLE:
                        case KEY_VALUE:
                                match (current_token);
                                break;

                        default:
                                return 1;
                }
        }
}



static int
rule_action (void)
{
        int len;
        
        while (1){
                switch (current_token){

                        case SQUOTED:
                        case DQUOTED:
                                return 0;

                        case IDENTIFIER:
                                if (strcmp (yytext, "SUBJECT") == 0
                                    || strcmp (yytext, "TO") == 0
                                    || strcmp (yytext, "FROM") == 0
                                    || strcmp (yytext, "CC") == 0
                                    || strcmp (yytext, "TOCC") == 0)
                                        return 0;
                                match (IDENTIFIER);
                                break;

                        case KEY_CONTROL:
                        case KEY_SIMPLE:
                        case KEY_VALUE:
                        case EOL:
                                match (current_token);
                                break;

                        case TEXT:
                                match (TEXT);
                                len = strlen (token_txt);
                                if (len < 2)
                                        break;
                                if (token_txt[len - 1] == '.'){
                                        token_txt[len - 1] = '\0';
                                        procmail_setup_action (token_txt, 1);
                                        goto eat_empty_tokens;
                                }
                                else if (token_txt[len - 1] == ';'){
                                        token_txt[len - 1] = '\0';
                                        procmail_setup_action (token_txt, 0);
                                        goto eat_empty_tokens;
                                }
                                break;
                                        
                        default:
                                return 1;
                }
        }

  eat_empty_tokens:
        while (current_token == EOL)
                match (EOL);
        return 0;
}



static int
rule_content (void)
{
        do {
                if (rule_header ())
                        return 1;
                if (rule_constraint ())
                        return 1;
                if (rule_action ())
                        return 1;
        }
        while (current_token != EOF && current_token != '}');

        return 0;
}



static int
rule_command (void)
{
        if (match (KW_RULE))
                return 1;

        if (match (IDENTIFIER))
                return 1;

        procmail_setup_name (token_txt);

        if (match ('{')){
                return 1;
        }
        
        if (rule_content ()){
                return 1;
        }

        return match ('}');
}



static int
translate_charset (void)
{
        char *txt;
        
        switch (current_token){

                case IDENTIFIER:
                case SQUOTED:
                case DQUOTED:
                case TEXT:
                case KEY_SIMPLE:
                        match (current_token);
                        txt = expand (token_txt);
                        mime_add_charset (txt);
                        return 0;

                default:
                        return 1;
        }
}



static int
translate_command (void)
{
        if (match (KW_TRANSLATE))
                return 1;

        if (translate_charset ())
                return 1;

        if (translate_charset ())
                return 1;

        return match (EOL);
}



static char *
mime_value (void)
{
        char *txt;

        switch (current_token){

                case IDENTIFIER:
                case SQUOTED:
                case DQUOTED:
                case TEXT:
                case KEY_SIMPLE:
                        match (current_token);
                        txt = expand (token_txt);
                        return txt;

                default:
                        return NULL;
        }
}



static int
mime_command (void)
{
        char *type;
        char *re;
        
        if (match (KW_MIME))
                return 1;

        type = mime_value ();
        if (type == NULL)
                return 1;

        re = mime_value ();
        if (re == NULL)
                return 1;
        
        mime_register_type (type, re);
        return match (EOL);
}



static int
handler_command (void)
{
        char *type;
        char *handler;
        
        if (match (KW_HANDLER))
                return 1;

        type = mime_value ();
        if (type == NULL)
                return 1;

        handler = mime_value ();
        if (handler == NULL)
                return 1;

        mime_register_handler (type, handler);
        return match (EOL);
}



static int
property_arg (void)
{
        switch (current_token){

                case TEXT:
                case IDENTIFIER:
                        match (current_token);
                        property_add_arg (token_txt);
                        return 0;

                case EOL:
                        match (EOL);
                        return 0;
                
                default:
                        return 1;
        }
}



static int
property_args (void)
{
        do {
                if (property_arg ())
                        return 1;
        }
        while (current_token == TEXT || current_token == IDENTIFIER
               || current_token == EOL);

        return 0;
}



static int
property_rule_cond (void)
{
        char *txt;
        
        switch (current_token){

                case SQUOTED:
                        match (SQUOTED);
                        txt = expand (token_txt);
                        property_add_cond_str (txt);
                        xfree (txt);
                        return 0;

                case DQUOTED:
                        match (DQUOTED);
                        txt = expand (token_txt);
                        property_add_cond_re (txt);
                        xfree (txt);
                        return 0;

                case EOL:
                        match (EOL);
                        return 0;
                
                default:
                        return 1;
        }
}



static int
property_rule_conds (void)
{
        do {
                if (property_rule_cond ())
                        return 1;
        }
        while (current_token == SQUOTED || current_token == DQUOTED
               || current_token == EOL);

        return 0;
}



static int
property_rule_val (void)
{
        char *txt;
        
        switch (current_token){
                
                case IDENTIFIER:
                case TEXT:
                case SQUOTED:
                case DQUOTED:
                case KEY_SIMPLE:
                        match (current_token);
                        txt = expand (token_txt);
                        property_add_value (txt);
                        return 0;

                default:
                        return 1;
        }
}



static int
property_rule (void)
{
        if (property_rule_conds ())
                return 1;

        if (match (ARROW))
                return 1;

        if (property_rule_val ())
                return 1;

        return match (EOL);
}



static int
property_body (void)
{
        do {
                if (property_rule ())
                        return 1;
        }
        while (current_token == SQUOTED || current_token == DQUOTED
               || current_token == EOL);

        return 0;
}



static int
property_name (void)
{
        switch (current_token){

                case IDENTIFIER:
                case TEXT:
                        match (current_token);
                        property_new (token_txt);
                        return 0;

                default:
                        return 1;
        }
}



static int
property_command (void)
{
        if (match (KW_PROPERTY))
                return 1;

        if (property_name ())
                return 1;

        if (match ('('))
                return 1;

        if (property_args ())
                return 1;

        if (match (')'))
                return 1;

        if (match ('{'))
                return 1;

        if (match (EOL))
                return 1;
        
        if (property_body ())
                return 1;

        if (match ('}'))
                return 1;

        return 0;
}



static int
macro_comment (void)
{
        char *txt;
        
        switch (current_token){

                case SQUOTED:
                case DQUOTED:
                        match (current_token);
                        txt = expand (token_txt);
                        exec_macro_comment (txt);
                        return match (EOL);

                default:
                        return 0;
        }
}


static int
macro_instruction (void)
{
        switch (current_token){

                case IDENTIFIER:
                        match (IDENTIFIER);
                        exec_macro_add_hook (token_txt);
                        return match (EOL);

                case EOL:
                        match (EOL);
                        return 0;

                default:
                        return 1;
        }
}



static int
macro_body (void)
{
        do {
                if (macro_instruction ())
                        return 1;
        }
        while (current_token == IDENTIFIER || current_token == EOL);

        return 0;
}



static int
macro_command (void)
{
        if (match (KW_MACRO))
                return 1;

        if (match (IDENTIFIER))
                return 1;

        exec_new_macro (token_txt);

        if (match ('{'))
                return 1;

        if (match (EOL))
                return 1;

        if (macro_comment ())
                return 1;
        
        if (macro_body ())
                return 1;

        exec_macro_commit ();
        
        if (match ('}'))
                return 1;
        
        return 0;
}



static int
command (void)
{
        switch (current_token){

                case KW_SET:
                        return set_command ();

                case KW_HOOK:
                        return hook_command ();

                case KW_KEY:
                        return key_command ();

                case KW_ADDR:
                        return addr_command ();

                case KW_INCLUDE:
                        return include_command ();

                case KW_RULE:
                        return rule_command ();

                case KW_TRANSLATE:
                        return translate_command ();

                case KW_MIME:
                        return mime_command ();

                case KW_HANDLER:
                        return handler_command ();

                case KW_PROPERTY:
                        return property_command ();

                case KW_MACRO:
                        return macro_command ();

                case EOL:
                        return match (EOL);

                default:
                        return 1;
        }
}


static int
command_list (void)
{
        while (current_token != EOF){
                if (command ()){
                        return 1;
                }
        }
        return 0;
}



static int
parse (void)
{
        if (command_list ()){
                parse_error ();
                return 1;
        }
        return match (EOF);
}

/****************************************************************************
 *    INTERFACE CLASS BODIES
 ****************************************************************************/
/****************************************************************************
 *
 *    END MODULE confread.l
 *
 ****************************************************************************/


syntax highlighted by Code2HTML, v. 0.9.1