%{


#include "AppHdr.h"
#include <map>
#include <algorithm>
#include "end.h"
#include "l-defs.h"
#include "mapdef.h"
#include "maps.h"
#include "stringutil.h"

#define YYERROR_VERBOSE 1
#define YYENABLE_NLS 0
#define YYLTYPE_IS_TRIVIAL 0

int yylex();

extern int yylineno;

static NORETURN void yyerror(const char *e, bool dlua_error = false)
{
    if (strstr(e, lc_desfile.c_str()) == e)
        fprintf(stderr, "%s\n", e);
    else
        fprintf(stderr, "%s:%d: %s\n", lc_desfile.c_str(), yylineno, e);
    if (dlua_error && !dlua_errors.empty())
        fprintf(stderr, "%s\n", dlua_errors.back().stack_trace.c_str());
    // Bail bail bail.
    end(1);
}

%}

%union
{
    int i;
    double f;
    const char *text;
}

/* Two harmless shift/reduce conflicts */
%expect 2

%token <i>      DEFAULT_DEPTH SHUFFLE CLEAR SUBST TAGS KFEAT KITEM KMONS KMASK KPROP
%token <i>      NAME DEPTH ORIENT PLACE CHANCE WEIGHT MONS ITEM MARKER COLOUR
%token <i>      PRELUDE MAIN VALIDATE VETO EPILOGUE NSUBST WELCOME
%token <i>      LFLOORCOL LROCKCOL LFLOORTILE LROCKTILE FTILE RTILE TILE
%token <i>      SUBVAULT FHEIGHT DESC ORDER

%token <i>      COMMA COLON PERC DASH CHARACTER
%token <f>      NUMBER

%token <text>   STRING MAP_LINE MONSTER_NAME ITEM_INFO
%token <text>   LUA_LINE
%type <i>       chance_roll

%%

file            : definitions { }
                ;

definitions     : /* empty */               {}
                | definitions definition    {}
                ;

definition      : def       {}
                | level     {}
                ;

error_seq       : CHARACTER
                {
                    yyerror("Unexpected character sequence.");
                }
                ;

def             : defdepth  {}
                | global_lua  {}
                ;

defdepth        : DEFAULT_DEPTH STRING
                {
                    dgn_reset_default_depth();
                    string err = dgn_set_default_depth($2);
                    if (!err.empty())
                        yyerror(make_stringf("Bad default-depth: %s (%s)",
                                $2, err.c_str()).c_str());
                }
                ;

level           : name map_specs
                {
                    lc_map.set_file(lc_desfile);

                    if (lc_run_global_prelude && !lc_global_prelude.empty())
                    {
                        lc_global_prelude.set_file(lc_desfile);
                        lc_run_global_prelude = false;
                        if (lc_global_prelude.load_call(dlua, nullptr))
                            yyerror( lc_global_prelude.orig_error().c_str() );
                    }

                    string err = lc_map.validate_map_def();
                    dump_map(lc_map);
                    if (!err.empty())
                        yyerror(err.c_str(), true);
                    add_parsed_map(lc_map);
                }
                ;

map_specs       : /* nothing */      { }
                | map_specs map_spec { }
                ;

map_spec        : metaline { }
                | map_def  { }
                ;

name            : NAME STRING
                {
                    lc_map.init();
                    lc_map.name = $2;

                    map_load_info_t::const_iterator i =
                        lc_loaded_maps.find($2);

                    if (i != lc_loaded_maps.end())
                    {
                        yyerror(
                            make_stringf(
                                "Map named '%s' already loaded at %s:%d",
                                $2,
                                i->second.filename.c_str(),
                                i->second.lineno).c_str() );
                    }

                    lc_map.place_loaded_from =
                        map_file_place(lc_desfile, yylineno);
                    lc_loaded_maps[$2] = lc_map.place_loaded_from;
                }
                ;

metaline        : place
                | desc
                | order
                | depth
                | chance
                | weight
                | orientation
                | welcome
                | mons
                | items
                | marker
                | subst
                | nsubst
                | colour
                | lfloorcol
                | lrockcol
                | lfloortile
                | lrocktile
                | ftile
                | rtile
                | tile
                | shuffle
                | clear
                | tags
                | kfeat
                | kitem
                | kmons
                | kmask
                | kprop
                | fheight
                | subvault
                | main_lua
                | prelude_lua
                | validate_lua
                | veto_lua
                | epilogue_lua
                | error_seq {}
                ;

global_lua      : MAIN global_lua_lines { }

global_lua_lines  : /* empty */ { }
                | global_lua_lines global_lua_line { }
                ;

global_lua_line   : LUA_LINE
                {
                    lc_global_prelude.add(yylineno, $1);
                }

main_lua        : MAIN main_lua_lines { }

main_lua_lines  : /* empty */ { }
                | main_lua_lines main_lua_line { }
                ;

main_lua_line   : LUA_LINE
                {
                    lc_map.main.add(yylineno, $1);
                }

validate_lua    : VALIDATE validate_lua_lines { }

validate_lua_lines  : /* empty */ { }
                | validate_lua_lines validate_lua_line { }
                ;

validate_lua_line   : LUA_LINE
                {
                    lc_map.validate.add(yylineno, $1);
                }

veto_lua        : VETO veto_lua_lines { }

veto_lua_lines  : /* empty */ { }
                | veto_lua_lines veto_lua_line { }
                ;

veto_lua_line   : LUA_LINE
                {
                    lc_map.veto.add(yylineno, $1);
                }

prelude_lua     : PRELUDE prelude_lua_lines { }

prelude_lua_lines : /* empty */ { }
                | prelude_lua_lines prelude_lua_line { }
                ;

prelude_lua_line : LUA_LINE
                {
                    lc_map.prelude.add(yylineno, $1);
                }

epilogue_lua    : EPILOGUE epilogue_lua_lines { }

epilogue_lua_lines : /* empty */ { }
                | epilogue_lua_lines epilogue_lua_line { }
                ;

epilogue_lua_line : LUA_LINE
                {
                    lc_map.epilogue.add(yylineno, $1);
                }

kfeat           : KFEAT { }
                | KFEAT STRING
                {
                    lc_map.main.add(
                        yylineno,
                        make_stringf("kfeat(\"%s\")",
                            quote_lua_string($2).c_str()));
                }

kmons           : KMONS { }
                | KMONS STRING
                {
                    lc_map.main.add(
                        yylineno,
                        make_stringf("kmons(\"%s\")",
                            quote_lua_string($2).c_str()));
                }

kitem           : KITEM { }
                | KITEM STRING
                {
                    lc_map.main.add(
                        yylineno,
                        make_stringf("kitem(\"%s\")",
                            quote_lua_string($2).c_str()));
                }

kmask           : KMASK { }
                | KMASK STRING
                {
                    lc_map.main.add(
                        yylineno,
                        make_stringf("kmask(\"%s\")",
                            quote_lua_string($2).c_str()));
                }

kprop           : KPROP { }
                | KPROP STRING
                {
                    lc_map.main.add(
                        yylineno,
                        make_stringf("kprop(\"%s\")",
                            quote_lua_string($2).c_str()));
                }

shuffle         : SHUFFLE shuffle_specifiers {}
                ;

shuffle_specifiers : shuffle_spec
                   | shuffle_specifiers COMMA shuffle_spec
                   ;

shuffle_spec    : ITEM_INFO
                {
                    lc_map.main.add(
                        yylineno,
                        make_stringf("shuffle(\"%s\")",
                            quote_lua_string($1).c_str()));
                }

clear           : CLEAR STRING
                {
                    lc_map.main.add(
                        yylineno,
                        make_stringf("clear(\"%s\")",
                            quote_lua_string($2).c_str()));
                }
                ;

tags            : TAGS tagstrings {}
                ;

tagstrings      : /* empty */
                | tagstrings tagstring
                ;

tagstring       : STRING
                {
                    lc_map.main.add(
                        yylineno,
                        make_stringf("tags(\"%s\")",
                            quote_lua_string($1).c_str()));
                }
                ;

marker          : MARKER STRING
                {
                    string key, arg;
                    int sep(0);

                    const string err =
                      mapdef_split_key_item($2, &key, &sep, &arg);

                    if (!err.empty())
                      yyerror(err.c_str());

                    // Special treatment for Lua markers.
                    if (arg.find("lua:") == 0)
                    {
                       arg = arg.substr(4);
                       lc_map.main.add(
                         yylineno,
                         make_stringf("lua_marker(\"%s\", function () "
                                      "  return %s "
                                      "end)",
                                      quote_lua_string(key).c_str(),
                                      arg.c_str()));
                    }
                    else
                    {
                       lc_map.main.add(
                         yylineno,
                         make_stringf("marker(\"%s\")",
                                      quote_lua_string($2).c_str()));
                    }
                }
                ;

colour          : COLOUR colour_specifiers { }
                ;

lfloorcol       : LFLOORCOL { }
                | LFLOORCOL STRING
                {
                    lc_map.main.add(
                        yylineno,
                        make_stringf("lfloorcol(\"%s\")",
                            quote_lua_string($2).c_str()));
                }

lrockcol        : LROCKCOL { }
                | LROCKCOL STRING
                {
                    lc_map.main.add(
                        yylineno,
                        make_stringf("lrockcol(\"%s\")",
                            quote_lua_string($2).c_str()));
                }

lfloortile      : LFLOORTILE { }
                | LFLOORTILE STRING
                {
                    lc_map.main.add(
                        yylineno,
                        make_stringf("lfloortile(\"%s\")",
                            quote_lua_string($2).c_str()));
                }
                ;

lrocktile       : LROCKTILE { }
                | LROCKTILE STRING
                {
                    lc_map.main.add(
                        yylineno,
                        make_stringf("lrocktile(\"%s\")",
                            quote_lua_string($2).c_str()));
                }
                ;

fheight         : FHEIGHT fheight_specifiers

fheight_specifiers: fheight_specifier
                    | fheight_specifiers COMMA fheight_specifier
                    ;

fheight_specifier : ITEM_INFO
                {
                    lc_map.main.add(
                        yylineno,
                        make_stringf("fheight(\"%s\")",
                            quote_lua_string($1).c_str()));
                }
                ;

ftile           : FTILE ftile_specifiers
                ;

ftile_specifiers: ftile_specifier
                | ftile_specifiers COMMA ftile_specifier
                ;

ftile_specifier : ITEM_INFO
                {
                    lc_map.main.add(
                        yylineno,
                        make_stringf("ftile(\"%s\")",
                            quote_lua_string($1).c_str()));
                }
                ;

rtile           : RTILE rtile_specifiers
                ;

rtile_specifiers: rtile_specifier
                | rtile_specifiers COMMA rtile_specifier
                ;

rtile_specifier : ITEM_INFO
                {
                    lc_map.main.add(
                        yylineno,
                        make_stringf("rtile(\"%s\")",
                            quote_lua_string($1).c_str()));
                }
                ;

tile            : TILE tile_specifiers
                ;

tile_specifiers : tile_specifier
                | tile_specifiers COMMA tile_specifier
                ;

tile_specifier  : ITEM_INFO
                {
                    lc_map.main.add(
                        yylineno,
                        make_stringf("tile(\"%s\")",
                            quote_lua_string($1).c_str()));
                }
                ;


colour_specifiers : colour_specifier  { }
                  | colour_specifiers COMMA colour_specifier { }
                  ;

colour_specifier  : ITEM_INFO
                  {
                      lc_map.main.add(
                          yylineno,
                          make_stringf("colour(\"%s\")",
                              quote_lua_string($1).c_str()));
                  }
                  ;

nsubst          : NSUBST nsubst_specifiers { }
                ;

nsubst_specifiers : nsubst_spec  { }
                  | nsubst_specifiers COMMA nsubst_spec { }
                  ;

nsubst_spec     : ITEM_INFO
                {
                    lc_map.main.add(
                        yylineno,
                        make_stringf("nsubst(\"%s\")",
                            quote_lua_string($1).c_str()));
                }
                ;

subst           : SUBST subst_specifiers { }
                ;

subst_specifiers : subst_spec
                 | subst_spec COMMA subst_specifiers
                 ;

subst_spec      : ITEM_INFO
                {
                    lc_map.main.add(
                        yylineno,
                        make_stringf("subst(\"%s\")",
                            quote_lua_string($1).c_str()));
                }
                ;

items           : ITEM {}
                | ITEM item_specifiers {}
                ;

item_specifiers : item_specifiers COMMA item_specifier
                | item_specifier
                ;

item_specifier  : ITEM_INFO
                {
                    lc_map.main.add(
                        yylineno,
                        make_stringf("item(\"%s\")",
                            quote_lua_string($1).c_str()));
                }

mons            : MONS {}
                | MONS mnames {}
                ;

mnames          : mname COMMA mnames
                | mname
                ;

mname           : MONSTER_NAME
                {
                    lc_map.main.add(
                        yylineno,
                        make_stringf("mons(\"%s\")",
                            quote_lua_string($1).c_str()));
                }
                ;

place           : PLACE STRING
                {
                    lc_map.main.add(
                        yylineno,
                        make_stringf("place(\"%s\")",
                            quote_lua_string($2).c_str()));
                }
                ;

desc            : DESC STRING
                {
                    lc_map.main.add(
                        yylineno,
                        make_stringf("desc(\"%s\")",
                            quote_lua_string($2).c_str()));
                }
                ;

order           : ORDER NUMBER
                {
                    lc_map.main.add(
                        yylineno,
                        make_stringf("order(%d)", int($2)));
                }
                ;

depth           : DEPTH {}
                | DEPTH STRING
                {
                    lc_map.main.add(
                        yylineno,
                        make_stringf("depth(\"%s\")",
                            quote_lua_string($2).c_str()));
                }
                ;

chance          : CHANCE chance_specifiers { }
                | CHANCE { }
                ;

chance_specifiers : chance_specifiers COMMA chance_specifier
                  | chance_specifier;

chance_roll     : NUMBER PERC
                {
                    $$ = $1 * 100;
                }
                | NUMBER
                {
                    $$ = $1;
                }

chance_specifier : chance_roll STRING
                {
                    lc_map.main.add(
                        yylineno,
                        make_stringf("depth_chance(\"%s\", %d)",
                                     quote_lua_string($2).c_str(), (int)$1));
                }
                | chance_roll
                {
                    lc_map.main.add(
                        yylineno,
                        make_stringf("chance(%d)", (int)$1));
                }
                ;

weight          : WEIGHT weight_specifiers;

weight_specifiers : weight_specifiers COMMA weight_specifier
                  | weight_specifier;

weight_specifier : NUMBER STRING
                 {
                     lc_map.main.add(
                         yylineno,
                         make_stringf("depth_weight(\"%s\", %d)",
                                      quote_lua_string($2).c_str(), (int)$1));
                 }
                 | NUMBER
                 {
                     lc_map.main.add(
                         yylineno,
                         make_stringf("weight(%d)", (int)$1));
                 }
                 ;

orientation     : ORIENT {}
                | ORIENT STRING
                {
                    lc_map.main.add(
                        yylineno,
                        make_stringf("orient(\"%s\")",
                                     quote_lua_string($2).c_str()));
                }
                ;

welcome         : WELCOME STRING
                {
                    lc_map.main.add(
                        yylineno,
                        make_stringf("welcome(\"%s\")",
                                     quote_lua_string($2).c_str()));
                }
                ;

map_def         : map_lines
                ;

map_lines       : map_lines map_line
                | map_line
                ;

map_line        : MAP_LINE
                {
                    lc_map.mapchunk.add(
                        yylineno,
                        make_stringf("map(\"%s\")",
                                     quote_lua_string($1).c_str()));
                }
                ;

subvault        : SUBVAULT subvault_specifiers
                ;

subvault_specifiers: subvault_specifier
                   | subvault_specifiers COMMA subvault_specifier
                   ;

subvault_specifier : STRING
                   {
                       lc_map.main.add(
                           yylineno,
                           make_stringf("subvault(\"%s\")",
                               quote_lua_string($1).c_str()));
                   }
                   ;

%%
