/**
 * @file NCDConfigParser.y
 * @author Ambroz Bizjak <ambrop7@gmail.com>
 * 
 * @section LICENSE
 * 
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 * 3. Neither the name of the author nor the
 *    names of its contributors may be used to endorse or promote products
 *    derived from this software without specific prior written permission.
 * 
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
 * ANY EXPRESS 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 AUTHOR 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.
 */

%include {

#include <string.h>
#include <stddef.h>

#include <misc/debug.h>
#include <misc/concat_strings.h>
#include <ncd/NCDAst.h>

struct parser_out {
    int out_of_memory;
    int syntax_error;
    int have_ast;
    NCDProgram ast;
};

struct token {
    char *str;
    size_t len;
};

struct program {
    int have;
    NCDProgram v;
};

struct block {
    int have;
    NCDBlock v;
};

struct statement {
    int have;
    NCDStatement v;
};

struct ifblock {
    int have;
    NCDIfBlock v;
};

struct value {
    int have;
    NCDValue v;
};

static void free_token (struct token o) { free(o.str); }
static void free_program (struct program o) { if (o.have) NCDProgram_Free(&o.v); }
static void free_block (struct block o) { if (o.have) NCDBlock_Free(&o.v); }
static void free_statement (struct statement o) { if (o.have) NCDStatement_Free(&o.v); }
static void free_ifblock (struct ifblock o) { if (o.have) NCDIfBlock_Free(&o.v); }
static void free_value (struct value o) { if (o.have) NCDValue_Free(&o.v); }

}

%extra_argument { struct parser_out *parser_out }

%token_type { struct token }

%token_destructor { free_token($$); }

%type processes { struct program }
%type statement { struct statement }
%type elif_maybe { struct ifblock }
%type elif { struct ifblock }
%type else_maybe { struct block }
%type statements { struct block }
%type dotted_name { char * }
%type statement_args_maybe { struct value }
%type list_contents { struct value }
%type list { struct value }
%type map_contents { struct value }
%type map  { struct value }
%type value  { struct value }
%type name_maybe { char * }
%type process_or_template { int }

// mention parser_out in some destructor to a void unused variable warning
%destructor processes { (void)parser_out; free_program($$); }
%destructor statement { free_statement($$); }
%destructor elif_maybe { free_ifblock($$); }
%destructor elif { free_ifblock($$); }
%destructor else_maybe { free_block($$); }
%destructor statements { free_block($$); }
%destructor dotted_name { free($$); }
%destructor statement_args_maybe { free_value($$); }
%destructor list_contents { free_value($$); }
%destructor list { free_value($$); }
%destructor map_contents { free_value($$); }
%destructor map { free_value($$); }
%destructor value { free_value($$); }
%destructor name_maybe { free($$); }

%stack_size 0

%syntax_error {
    parser_out->syntax_error = 1;
}

// workaroud Lemon bug: if the stack overflows, the token that caused the overflow will be leaked
%stack_overflow {
    if (yypMinor) {
        free_token(yypMinor->yy0);
    }
}

input ::= processes(A). {
    ASSERT(!parser_out->have_ast)

    if (A.have) {
        parser_out->have_ast = 1;
        parser_out->ast = A.v;
    }
}

processes(R) ::= . {
    NCDProgram prog;
    NCDProgram_Init(&prog);
    
    R.have = 1;
    R.v = prog;
}

processes(R) ::= INCLUDE STRING(A) processes(N). {
    ASSERT(A.str)
    if (!N.have) {
        goto failA0;
    }
    
    NCDProgramElem elem;
    if (!NCDProgramElem_InitInclude(&elem, A.str, A.len)) {
        goto failA0;
    }
    
    if (!NCDProgram_PrependElem(&N.v, elem)) {
        goto failA1;
    }
    
    R.have = 1;
    R.v = N.v;
    N.have = 0;
    goto doneA;

failA1:
    NCDProgramElem_Free(&elem);
failA0:
    R.have = 0;
    parser_out->out_of_memory = 1;
doneA:
    free_token(A);
    free_program(N);
}

processes(R) ::= INCLUDE_GUARD STRING(A) processes(N). {
    ASSERT(A.str)
    if (!N.have) {
        goto failZ0;
    }
    
    NCDProgramElem elem;
    if (!NCDProgramElem_InitIncludeGuard(&elem, A.str, A.len)) {
        goto failZ0;
    }
    
    if (!NCDProgram_PrependElem(&N.v, elem)) {
        goto failZ1;
    }
    
    R.have = 1;
    R.v = N.v;
    N.have = 0;
    goto doneZ;

failZ1:
    NCDProgramElem_Free(&elem);
failZ0:
    R.have = 0;
    parser_out->out_of_memory = 1;
doneZ:
    free_token(A);
    free_program(N);
}

processes(R) ::= process_or_template(T) NAME(A) CURLY_OPEN statements(B) CURLY_CLOSE processes(N). {
    ASSERT(A.str)
    if (!B.have || !N.have) {
        goto failB0;
    }

    NCDProcess proc;
    if (!NCDProcess_Init(&proc, T, A.str, B.v)) {
        goto failB0;
    }
    B.have = 0;
    
    NCDProgramElem elem;
    NCDProgramElem_InitProcess(&elem, proc);

    if (!NCDProgram_PrependElem(&N.v, elem)) {
        goto failB1;
    }

    R.have = 1;
    R.v = N.v;
    N.have = 0;
    goto doneB;

failB1:
    NCDProgramElem_Free(&elem);
failB0:
    R.have = 0;
    parser_out->out_of_memory = 1;
doneB:
    free_token(A);
    free_block(B);
    free_program(N);
}

statement(R) ::= dotted_name(A) ROUND_OPEN statement_args_maybe(B) ROUND_CLOSE name_maybe(C) SEMICOLON. {
    if (!A || !B.have) {
        goto failC0;
    }

    if (!NCDStatement_InitReg(&R.v, C, NULL, A, B.v)) {
        goto failC0;
    }
    B.have = 0;

    R.have = 1;
    goto doneC;

failC0:
    R.have = 0;
    parser_out->out_of_memory = 1;
doneC:
    free(A);
    free_value(B);
    free(C);
}

statement(R) ::= dotted_name(M) ARROW dotted_name(A) ROUND_OPEN statement_args_maybe(B) ROUND_CLOSE name_maybe(C) SEMICOLON. {
    if (!M || !A || !B.have) {
        goto failD0;
    }

    if (!NCDStatement_InitReg(&R.v, C, M, A, B.v)) {
        goto failD0;
    }
    B.have = 0;

    R.have = 1;
    goto doneD;

failD0:
    R.have = 0;
    parser_out->out_of_memory = 1;
doneD:
    free(M);
    free(A);
    free_value(B);
    free(C);
}

statement(R) ::= IF ROUND_OPEN value(A) ROUND_CLOSE CURLY_OPEN statements(B) CURLY_CLOSE elif_maybe(I) else_maybe(E) name_maybe(C) SEMICOLON. {
    if (!A.have || !B.have || !I.have) {
        goto failE0;
    }

    NCDIf ifc;
    NCDIf_Init(&ifc, A.v, B.v);
    A.have = 0;
    B.have = 0;

    if (!NCDIfBlock_PrependIf(&I.v, ifc)) {
        NCDIf_Free(&ifc);
        goto failE0;
    }

    if (!NCDStatement_InitIf(&R.v, C, I.v)) {
        goto failE0;
    }
    I.have = 0;

    if (E.have) {
        NCDStatement_IfAddElse(&R.v, E.v);
        E.have = 0;
    }

    R.have = 1;
    goto doneE;

failE0:
    R.have = 0;
    parser_out->out_of_memory = 1;
doneE:
    free_value(A);
    free_block(B);
    free_ifblock(I);
    free_block(E);
    free(C);
}

statement(R) ::= FOREACH ROUND_OPEN value(A) AS NAME(B) ROUND_CLOSE CURLY_OPEN statements(S) CURLY_CLOSE name_maybe(N) SEMICOLON. {
    if (!A.have || !B.str || !S.have) {
        goto failEA0;
    }
    
    if (!NCDStatement_InitForeach(&R.v, N, A.v, B.str, NULL, S.v)) {
        goto failEA0;
    }
    A.have = 0;
    S.have = 0;
    
    R.have = 1;
    goto doneEA0;
    
failEA0:
    R.have = 0;
    parser_out->out_of_memory = 1;
doneEA0:
    free_value(A);
    free_token(B);
    free_block(S);
    free(N);
}

statement(R) ::= FOREACH ROUND_OPEN value(A) AS NAME(B) COLON NAME(C) ROUND_CLOSE CURLY_OPEN statements(S) CURLY_CLOSE name_maybe(N) SEMICOLON. {
    if (!A.have || !B.str || !C.str || !S.have) {
        goto failEB0;
    }
    
    if (!NCDStatement_InitForeach(&R.v, N, A.v, B.str, C.str, S.v)) {
        goto failEB0;
    }
    A.have = 0;
    S.have = 0;
    
    R.have = 1;
    goto doneEB0;
    
failEB0:
    R.have = 0;
    parser_out->out_of_memory = 1;
doneEB0:
    free_value(A);
    free_token(B);
    free_token(C);
    free_block(S);
    free(N);
}

elif_maybe(R) ::= . {
    NCDIfBlock_Init(&R.v);
    R.have = 1;
}

elif_maybe(R) ::= elif(A). {
    R = A;
}

elif(R) ::= ELIF ROUND_OPEN value(A) ROUND_CLOSE CURLY_OPEN statements(B) CURLY_CLOSE. {
    if (!A.have || !B.have) {
        goto failF0;
    }

    NCDIfBlock_Init(&R.v);

    NCDIf ifc;
    NCDIf_Init(&ifc, A.v, B.v);
    A.have = 0;
    B.have = 0;

    if (!NCDIfBlock_PrependIf(&R.v, ifc)) {
        goto failF1;
    }

    R.have = 1;
    goto doneF0;

failF1:
    NCDIf_Free(&ifc);
    NCDIfBlock_Free(&R.v);
failF0:
    R.have = 0;
    parser_out->out_of_memory = 1;
doneF0:
    free_value(A);
    free_block(B);
}

elif(R) ::= ELIF ROUND_OPEN value(A) ROUND_CLOSE CURLY_OPEN statements(B) CURLY_CLOSE elif(N). {
    if (!A.have || !B.have || !N.have) {
        goto failG0;
    }

    NCDIf ifc;
    NCDIf_Init(&ifc, A.v, B.v);
    A.have = 0;
    B.have = 0;

    if (!NCDIfBlock_PrependIf(&N.v, ifc)) {
        goto failG1;
    }

    R.have = 1;
    R.v = N.v;
    N.have = 0;
    goto doneG0;

failG1:
    NCDIf_Free(&ifc);
failG0:
    R.have = 0;
    parser_out->out_of_memory = 1;
doneG0:
    free_value(A);
    free_block(B);
    free_ifblock(N);
}

else_maybe(R) ::= . {
    R.have = 0;
}

else_maybe(R) ::= ELSE CURLY_OPEN statements(B) CURLY_CLOSE. {
    R = B;
}

statements(R) ::= statement(A). {
    if (!A.have) {
        goto failH0;
    }

    NCDBlock_Init(&R.v);

    if (!NCDBlock_PrependStatement(&R.v, A.v)) {
        goto failH1;
    }
    A.have = 0;

    R.have = 1;
    goto doneH;

failH1:
    NCDBlock_Free(&R.v);
failH0:
    R.have = 0;
    parser_out->out_of_memory = 1;
doneH:
    free_statement(A);
}

statements(R) ::= statement(A) statements(N). {
    if (!A.have || !N.have) {
        goto failI0;
    }

    if (!NCDBlock_PrependStatement(&N.v, A.v)) {
        goto failI1;
    }
    A.have = 0;

    R.have = 1;
    R.v = N.v;
    N.have = 0;
    goto doneI;

failI1:
    NCDBlock_Free(&R.v);
failI0:
    R.have = 0;
    parser_out->out_of_memory = 1;
doneI:
    free_statement(A);
    free_block(N);
}

dotted_name(R) ::= NAME(A). {
    ASSERT(A.str)

    R = A.str;
}

dotted_name(R) ::= NAME(A) DOT dotted_name(N). {
    ASSERT(A.str)
    if (!N) {
        goto failJ0;
    }

    if (!(R = concat_strings(3, A.str, ".", N))) {
        goto failJ0;
    }

    goto doneJ;

failJ0:
    R = NULL;
    parser_out->out_of_memory = 1;
doneJ:
    free_token(A);
    free(N);
}

statement_args_maybe(R) ::= . {
    R.have = 1;
    NCDValue_InitList(&R.v);
}

statement_args_maybe(R) ::= list_contents(A). {
    R = A;
}

list_contents(R) ::= value(A). {
    if (!A.have) {
        goto failL0;
    }

    NCDValue_InitList(&R.v);

    if (!NCDValue_ListPrepend(&R.v, A.v)) {
        goto failL1;
    }
    A.have = 0;

    R.have = 1;
    goto doneL;

failL1:
    NCDValue_Free(&R.v);
failL0:
    R.have = 0;
    parser_out->out_of_memory = 1;
doneL:
    free_value(A);
}

list_contents(R) ::= value(A) COMMA list_contents(N). {
    if (!A.have || !N.have) {
        goto failM0;
    }

    if (!NCDValue_ListPrepend(&N.v, A.v)) {
        goto failM0;
    }
    A.have = 0;

    R.have = 1;
    R.v = N.v;
    N.have = 0;
    goto doneM;

failM0:
    R.have = 0;
    parser_out->out_of_memory = 1;
doneM:
    free_value(A);
    free_value(N);
}

list(R) ::= CURLY_OPEN CURLY_CLOSE. {
    R.have = 1;
    NCDValue_InitList(&R.v);
}

list(R) ::= CURLY_OPEN list_contents(A) CURLY_CLOSE. {
    R = A;
}

map_contents(R) ::= value(A) COLON value(B). {
    if (!A.have || !B.have) {
        goto failS0;
    }

    NCDValue_InitMap(&R.v);

    if (!NCDValue_MapPrepend(&R.v, A.v, B.v)) {
        goto failS1;
    }
    A.have = 0;
    B.have = 0;

    R.have = 1;
    goto doneS;

failS1:
    NCDValue_Free(&R.v);
failS0:
    R.have = 0;
    parser_out->out_of_memory = 1;
doneS:
    free_value(A);
    free_value(B);
}

map_contents(R) ::= value(A) COLON value(B) COMMA map_contents(N). {
    if (!A.have || !B.have || !N.have) {
        goto failT0;
    }

    if (!NCDValue_MapPrepend(&N.v, A.v, B.v)) {
        goto failT0;
    }
    A.have = 0;
    B.have = 0;

    R.have = 1;
    R.v = N.v;
    N.have = 0;
    goto doneT;

failT0:
    R.have = 0;
    parser_out->out_of_memory = 1;
doneT:
    free_value(A);
    free_value(B);
    free_value(N);
}

map(R) ::= BRACKET_OPEN BRACKET_CLOSE. {
    R.have = 1;
    NCDValue_InitMap(&R.v);
}

map(R) ::= BRACKET_OPEN map_contents(A) BRACKET_CLOSE. {
    R = A;
}

value(R) ::= STRING(A). {
    ASSERT(A.str)

    if (!NCDValue_InitStringBin(&R.v, (uint8_t *)A.str, A.len)) {
        goto failU0;
    }

    R.have = 1;
    goto doneU;

failU0:
    R.have = 0;
    parser_out->out_of_memory = 1;
doneU:
    free_token(A);
}

value(R) ::= dotted_name(A). {
    if (!A) {
        goto failV0;
    }

    if (!NCDValue_InitVar(&R.v, A)) {
        goto failV0;
    }

    R.have = 1;
    goto doneV;

failV0:
    R.have = 0;
    parser_out->out_of_memory = 1;
doneV:
    free(A);
}

value(R) ::= list(A). {
    R = A;
}

value(R) ::= map(A). {
    R = A;
}

name_maybe(R) ::= . {
    R = NULL;
}

name_maybe(R) ::= NAME(A). {
    ASSERT(A.str)

    R = A.str;
}

process_or_template(R) ::= PROCESS. {
    R = 0;
}

process_or_template(R) ::= TEMPLATE. {
    R = 1;
}
