%code requires {
/* pull in types to populate YYSTYPE: */
#include "src/parse/ast.h"
namespace re2c {
    struct AstNode;
    struct SemAct;
}
}

%{

#pragma GCC diagnostic push
#include "src/util/nowarn_in_bison.h"

#include "src/msg/msg.h"
#include "src/options/opt.h"
#include "src/parse/input.h"
#include "src/parse/parser.h"
#include "src/regexp/rule.h"

using namespace re2c;

extern "C" {
    static void yyerror(Input& input, Ast&, Opt&, AstGrams&, const char* s);
    static int yylex(YYSTYPE* yylval, Input& input, Ast& ast, Opt&, AstGrams&);
}

%}

%start spec

%define api.pure full
%define api.prefix {re2c_}
%lex-param {re2c::Input& input}
%lex-param {re2c::Ast& ast}
%lex-param {re2c::Opt& opts}
%lex-param {re2c::AstGrams& grams}
%parse-param {re2c::Input& input}
%parse-param {re2c::Ast& ast}
%parse-param {re2c::Opt& opts}
%parse-param {re2c::AstGrams& grams}

%union {
    const re2c::AstNode* regexp;
    const re2c::SemAct* semact;
    char op;
    re2c::AstBounds bounds;
    const char* cstr;
    std::string* str;
};

%token TOKEN_CJUMP
%token TOKEN_CNEXT
%token TOKEN_CLIST
%token TOKEN_CPRE_RULE
%token TOKEN_CZERO
%token TOKEN_CLOSESIZE
%token TOKEN_CODE
%token TOKEN_CONF
%token TOKEN_ID
%token TOKEN_FID
%token TOKEN_FID_END
%token TOKEN_LINE_INFO
%token TOKEN_REGEXP
%token TOKEN_BLOCK
%token TOKEN_ENTRY
%token TOKEN_PRE_RULE
%token TOKEN_POST_RULE
%token TOKEN_LPAREN_NEG
%token TOKEN_ERROR

%type <op>      close closes
%type <bounds>  TOKEN_CLOSESIZE
%type <semact>  TOKEN_CODE ccode
%type <regexp>  TOKEN_REGEXP trailexpr rule expr diff term factor primary
%type <cstr>    TOKEN_ID TOKEN_FID TOKEN_CNEXT TOKEN_CJUMP name

%%

spec
: %empty
| spec TOKEN_BLOCK {
    if (use_block(input, ast, opts, grams, ast.temp_blockname) != Ret::OK) YYABORT;
    ast.temp_blockname.clear();
}
| spec TOKEN_CONF {
    // Handled here, as the main lexer should not get access to opts under construction.
    if (input.lex_conf(opts) != Ret::OK) YYABORT;
}
| spec def
| spec rule
| spec TOKEN_LINE_INFO
;

def
: name expr enddef {
    if (add_named_def(opts.symtab, $1, $2, input) != Ret::OK) YYABORT;
}
| name expr '/' {
    input.error_at_tok("trailing contexts are not allowed in named definitions");
    YYABORT;
};

name
: TOKEN_ID '=' { $$ = $1; }
| TOKEN_FID
;

enddef: ';' | TOKEN_FID_END;

rule
: trailexpr TOKEN_CODE {
    find_or_add_gram(grams, "").rules.push_back({$1, $2});
}
| '*' TOKEN_CODE {
    find_or_add_gram(grams, "").defs.push_back($2);
}
| TOKEN_ENTRY TOKEN_CODE {
    find_or_add_gram(grams, "").entry.push_back($2);
}
| TOKEN_PRE_RULE TOKEN_CODE {
    find_or_add_gram(grams, "").pre_rule.push_back($2);
}
| TOKEN_POST_RULE TOKEN_CODE {
    find_or_add_gram(grams, "").post_rule.push_back($2);
}
| TOKEN_CLIST trailexpr ccode {
    for (const std::string& cond : ast.temp_condlist) {
        find_or_add_gram(grams, cond).rules.push_back({$2, $3});
    }
    ast.temp_condlist.clear();
}
| TOKEN_CLIST '*' ccode {
    for (const std::string& cond : ast.temp_condlist) {
        find_or_add_gram(grams, cond).defs.push_back($3);
    }
    ast.temp_condlist.clear();
}
| TOKEN_CLIST TOKEN_ENTRY ccode {
    for (const std::string& cond : ast.temp_condlist) {
        find_or_add_gram(grams, cond).entry.push_back($3);
    }
    ast.temp_condlist.clear();
}
| TOKEN_CLIST TOKEN_PRE_RULE ccode {
    for (const std::string& cond : ast.temp_condlist) {
        find_or_add_gram(grams, cond).pre_rule.push_back($3);
    }
    ast.temp_condlist.clear();
}
| TOKEN_CLIST TOKEN_POST_RULE ccode {
    for (const std::string& cond : ast.temp_condlist) {
        find_or_add_gram(grams, cond).post_rule.push_back($3);
    }
    ast.temp_condlist.clear();
}
| TOKEN_CPRE_RULE TOKEN_CODE {
    for (const std::string& cond : ast.temp_condlist) {
        find_or_add_gram(grams, cond).pre_rule.push_back($2);
    }
    ast.temp_condlist.clear();
}
| TOKEN_CZERO ccode {
    const AstNode* r = ast.nil(input.tok_loc());
    find_or_add_gram(grams, ZERO_COND).rules.push_back({r, $2});
    ast.temp_condlist.clear();
};

ccode
: TOKEN_CODE
| TOKEN_CNEXT TOKEN_CODE {
    $$ = $2;
    // parser needs to to update condition, but for the rest of the code actions are immutable
    const_cast<SemAct*>($$)->cond = $1; 
}
| TOKEN_CJUMP {
    $$ = ast.sem_act(input.tok_loc(), nullptr, $1, true);
};

trailexpr
: expr { $$ = ast.cap($1, CAPTURE_IF_NOT_INVERTED); }
| expr '/' expr {
    $$ = ast.cat(ast.cap($1, CAPTURE_IF_NOT_INVERTED),
                 ast.cat(ast.tag(input.tok_loc(), nullptr, false), $3));
};

expr
: diff
| expr '|' diff { $$ = ast.alt($1, $3); }
;

diff
: term
| diff '\\' term { $$ = ast.diff($1, $3); }
;

term
: factor
// in POSIX concatenation is right-associative
| factor term { $$ = ast.cat($1, $2); }
;

factor
: primary
| primary closes {
    switch($2) {
    case '*':
        $$ = ast.iter($1, 0, Ast::MANY);
        break;
    case '+':
        $$ = ast.iter($1, 1, Ast::MANY);
        break;
    case '?':
        $$ = ast.iter($1, 0, 1);
        break;
    }
}
| primary TOKEN_CLOSESIZE {
    $$ = ast.iter($1, $2.min, $2.max);
}
;

closes
: close
| closes close { $$ = ($1 == $2) ? $1 : '*'; }
;

close
: '*' { $$ = '*'; }
| '+' { $$ = '+'; }
| '?' { $$ = '?'; }
;

primary
: TOKEN_REGEXP
| TOKEN_ID {
    $$ = find_def(opts.symtab, $1);
    if ($$ == nullptr) {
        input.error_at_tok("undefined symbol '%s'", $1);
        YYABORT;
    } else if (Ast::needs_wrap($$)) {
        $$ = ast.cap($$, NO_CAPTURE);
    }
}
| '('              expr ')' { $$ = ast.cap($2, CAPTURE_IF_NOT_INVERTED); }
| TOKEN_LPAREN_NEG expr ')' { $$ = ast.cap($2, CAPTURE_IF_INVERTED); }
;

%%

#pragma GCC diagnostic pop

extern "C" {
    static void yyerror(Input& input, Ast&, Opt&, AstGrams&, const char* s) {
        input.error_at_cur("%s", s);
    }

    static int yylex(YYSTYPE* yylval, Input& input, Ast& ast, Opt&, AstGrams&) {
        int token;
        return input.lex_block(yylval, ast, token) == Ret::OK ? token : TOKEN_ERROR;
    }
}

namespace re2c {

Ret parse(Input& input, Ast& ast, Opt& opts, AstGrams& grams) {
    return yyparse(input, ast, opts, grams) == 0 ? Ret::OK : Ret::FAIL;
}

} // namespace re2c
