%code requires { #include "magic-interpreter.hpp" } // %code requires %code { #include "magic-interpreter-parser.hpp" #include #include // exception to "no va_list" rule, even after cxxstdio #include "../common/const_array.hpp" #include "../common/cxxstdio.hpp" #include "itemdb.hpp" #include "magic-expr.hpp" // I still don't get why this is necessary. #define YYLEX_PARAM 0, 0 // can't use src/warnings.hpp in generated code #pragma GCC diagnostic warning "-Wall" #pragma GCC diagnostic warning "-Wextra" #pragma GCC diagnostic warning "-Wformat" #ifndef __clang__ #pragma GCC diagnostic ignored "-Wmissing-field-initializers" #endif static size_t intern_id(ZString id_name); static dumb_ptr fun_expr(FString name, const_array> argv, int line, int column); static dumb_ptr dot_expr(dumb_ptr lhs, int id); static void BIN_EXPR(dumb_ptr& x, FString name, dumb_ptr arg1, dumb_ptr arg2, int line, int column) { dumb_ptr e[2]; e[0] = arg1; e[1] = arg2; x = fun_expr(name, const_array>(e, 2), line, column); } static int failed_flag = 0; static void magic_frontend_error(const char *msg); static __attribute__((format(printf, 3, 4))) void fail(int line, int column, const char *fmt, ...); static dumb_ptr new_spell(dumb_ptr guard); static dumb_ptr spellguard_implication(dumb_ptr a, dumb_ptr b); static dumb_ptr new_spellguard(SPELLGUARD ty); static dumb_ptr new_effect(EFFECT ty); static dumb_ptr set_effect_continuation(dumb_ptr src, dumb_ptr continuation); static void add_spell(dumb_ptr spell, int line_nr); static void add_teleport_anchor(dumb_ptr anchor, int line_nr); static dumb_ptr op_effect(FString name, const_array> argv, int line, int column); // in magic-interpreter-lexer.cpp int magic_frontend_lex(YYSTYPE *, YYLTYPE *); static void install_proc(dumb_ptr proc); static dumb_ptr call_proc(ZString name, dumb_ptr>> argvp, int line_nr, int column); static void bind_constant(FString name, val_t *val, int line_nr); static val_t *find_constant(FString name); } // %code %name-prefix="magic_frontend_" %locations %union { int i; SPELL_FLAG spell_flags; SPELLARG spell_arg; FOREACH_FILTER foreach_filter; dumb_string s; int op; // magic_conf_t *magic_conf; val_t value; dumb_ptr expr; e_location_t location; e_area_t area; args_rec_t arg_list; dumb_ptr> letdefvp; dumb_ptr spell; struct { int id; SPELLARG ty; } spellarg_def; letdef_t vardef; dumb_ptr spellguard; dumb_ptr components; struct { int id, count; } component; dumb_ptr effect; dumb_ptr proc; // evil hackery YYSTYPE() { really_memzero_this(this); } ~YYSTYPE() = default; YYSTYPE(const YYSTYPE& rhs) = default; YYSTYPE& operator = (const YYSTYPE& rhs) = default; } // %union %expect 7 %token INT %token STRING %token ID %token DIR %token '=' %token '<' %token '>' %token '+' %token '-' %token '*' %token '/' %token '%' %token '@' %token ',' %token '.' %token ':' %token ';' %token '|' %token '[' %token ']' %token '&' %token '^' %token CONST %token PROCEDURE %token CALL %token SILENT %token LOCAL %token NONMAGIC %token SHL %token SHR %token EQ %token NEQ %token GTE %token LTE %token ANDAND %token OROR %token SCRIPT_DATA %token TO %token TOWARDS %token TELEPORT_ANCHOR %token SPELL %token LET %token IN %token END %token DARROW %token STRING_TY %token REQUIRE %token CATALYSTS %token COMPONENTS %token MANA %token CASTTIME %token SKIP %token ABORT %token BREAK %token EFFECT_ %token ATEND %token ATTRIGGER %token PC_F %token NPC_F %token MOB_F %token ENTITY_F %token TARGET_F %token IF %token THEN %token ELSE %token FOREACH %token FOR %token DO %token SLEEP %type value %type location %type area %type arg_list %type arg_list_ne %type defs %type spelldef %type argopt %type def %type spellbody_list %type spellbody %type spellguard %type spellguard_list %type prereq %type item %type items %type item_list %type item_name %type selection; %type effect %type effect_list %type maybe_trigger %type maybe_end %type spell_flags; %type expr %type arg_ty %type proc_formals_list %type proc_formals_list_ne %left OROR %left ANDAND %left '<' '>' GTE LTE NEQ EQ %left '+' '-' %left '*' '/' '%' %left SHL SHR '&' '^' '|' %right '=' %left OR %left DARROW %left '.' %% spellconf : /* empty */ {} | spellconf_option semicolons spellconf {} ; semicolons : /* empty */ {} | semicolons ';' {} ; proc_formals_list : /* empty */ { $$ = dumb_ptr::make(); } | proc_formals_list_ne { $$ = $1; } ; proc_formals_list_ne : ID { $$ = dumb_ptr::make(); $$->argv.push_back(intern_id($1)); $1.delete_(); } | proc_formals_list_ne ',' ID { $$ = $1; $$->argv.push_back(intern_id($3)); $3.delete_(); } ; spellconf_option : ID '=' expr { if (find_constant($1.str())) { fail(@1.first_line, 0, "Attempt to redefine constant `%s' as global\n", $1.c_str()); } else { int var_id = intern_id($1); magic_eval(dumb_ptr(&magic_default_env), &magic_conf.varv[var_id].val, $3); } $1.delete_(); } | CONST ID '=' expr { val_t var; magic_eval(dumb_ptr(&magic_default_env), &var, $4); bind_constant($2.str(), &var, @1.first_line); $2.delete_(); } | TELEPORT_ANCHOR ID ':' expr '=' expr { auto anchor = dumb_ptr::make(); anchor->name = $2.str(); $2.delete_(); anchor->invocation = magic_eval_str(dumb_ptr(&magic_default_env), $4); anchor->location = $6; if (!failed_flag) add_teleport_anchor(anchor, @1.first_line); else anchor.delete_(); failed_flag = 0; } | PROCEDURE ID '(' proc_formals_list ')' '=' effect_list { dumb_ptr proc = $4; proc->name = $2.str(); $2.delete_(); proc->body = $7; if (!failed_flag) install_proc(proc); proc.delete_(); failed_flag = 0; } | spell_flags SPELL ID argopt ':' expr '=' spelldef { dumb_ptr spell = $8; spell->name = $3.str(); $3.delete_(); spell->invocation = magic_eval_str(dumb_ptr(&magic_default_env), $6); spell->arg = $4.id; spell->spellarg_ty = $4.ty; spell->flags = $1; if (!failed_flag) add_spell(spell, @1.first_line); failed_flag = 0; } ; spell_flags : /* empty */ { $$ = SPELL_FLAG::ZERO; } | LOCAL spell_flags { if (bool($2 & SPELL_FLAG::LOCAL)) fail(@1.first_line, @1.first_column, "`LOCAL' specified more than once"); $$ = $2 | SPELL_FLAG::LOCAL; } | NONMAGIC spell_flags { if (bool($2 & SPELL_FLAG::NONMAGIC)) fail(@1.first_line, @1.first_column, "`NONMAGIC' specified more than once"); $$ = $2 | SPELL_FLAG::NONMAGIC; } | SILENT spell_flags { if (bool($2 & SPELL_FLAG::SILENT)) fail(@1.first_line, @1.first_column, "`SILENT' specified more than once"); $$ = $2 | SPELL_FLAG::SILENT; } ; argopt : /* empty */ { $$.ty = SPELLARG::NONE; } | '(' ID ':' arg_ty ')' { $$.id = intern_id($2); $2.delete_(); $$.ty = $4; } ; arg_ty : PC_F { $$ = SPELLARG::PC; } | STRING_TY { $$ = SPELLARG::STRING; } ; value : DIR { $$.ty = TYPE::DIR; $$.v.v_int = $1; } | INT { $$.ty = TYPE::INT; $$.v.v_int = $1; } | STRING { $$.ty = TYPE::STRING; $$.v.v_string = $1; } ; expr : value { $$ = magic_new_expr(EXPR::VAL); $$->e.e_val = $1; } | ID { val_t *val = find_constant($1.str()); if (val) { $$ = magic_new_expr(EXPR::VAL); $$->e.e_val = *val; } else { $$ = magic_new_expr(EXPR::ID); $$->e.e_id = intern_id($1); } $1.delete_(); } | area { $$ = magic_new_expr(EXPR::AREA); $$->e.e_area = $1; } | expr '+' expr { BIN_EXPR($$, "+", $1, $3, @1.first_line, @1.first_column); } | expr '-' expr { BIN_EXPR($$, "-", $1, $3, @1.first_line, @1.first_column); } | expr '*' expr { BIN_EXPR($$, "*", $1, $3, @1.first_line, @1.first_column); } | expr '%' expr { BIN_EXPR($$, "%", $1, $3, @1.first_line, @1.first_column); } | expr '/' expr { BIN_EXPR($$, "/", $1, $3, @1.first_line, @1.first_column); } | expr '<' expr { BIN_EXPR($$, ">", $3, $1, @1.first_line, @1.first_column); } | expr '>' expr { BIN_EXPR($$, ">", $1, $3, @1.first_line, @1.first_column); } | expr '&' expr { BIN_EXPR($$, "&", $1, $3, @1.first_line, @1.first_column); } | expr '^' expr { BIN_EXPR($$, "^", $1, $3, @1.first_line, @1.first_column); } | expr '|' expr { BIN_EXPR($$, "|", $1, $3, @1.first_line, @1.first_column); } | expr SHL expr { BIN_EXPR($$, "<<", $1, $3, @1.first_line, @1.first_column); } | expr SHR expr { BIN_EXPR($$, ">>", $1, $3, @1.first_line, @1.first_column); } | expr LTE expr { BIN_EXPR($$, ">=", $3, $1, @1.first_line, @1.first_column); } | expr GTE expr { BIN_EXPR($$, ">=", $1, $3, @1.first_line, @1.first_column); } | expr ANDAND expr { BIN_EXPR($$, "&&", $1, $3, @1.first_line, @1.first_column); } | expr OROR expr { BIN_EXPR($$, "||", $1, $3, @1.first_line, @1.first_column); } | expr EQ expr { BIN_EXPR($$, "=", $1, $3, @1.first_line, @1.first_column); } | expr '=' expr { BIN_EXPR($$, "=", $1, $3, @1.first_line, @1.first_column); } | expr NEQ expr { BIN_EXPR($$, "=", $1, $3, @1.first_line, @1.first_column); $$ = fun_expr("not", const_array>(&$$, 1), @1.first_line, @1.first_column); } | ID '(' arg_list ')' { $$ = fun_expr($1.str(), *$3.argvp, @1.first_line, @1.first_column); $3.argvp.delete_(); $1.delete_(); // allocated from m-i-lexer.lpp } | '(' expr ')' { $$ = $2; } | expr '.' ID { $$ = dot_expr($1, intern_id($3)); $3.delete_(); } ; arg_list : /* empty */ { $$.argvp.new_(); } | arg_list_ne { $$ = $1; } ; arg_list_ne : expr { $$.argvp.new_(); $$.argvp->push_back($1); } | arg_list_ne ',' expr { // yikes! Fate is officially banned from ever touching my code again. $$ = $1; $$.argvp->push_back($3); } ; location : '@' '(' expr ',' expr ',' expr ')' { $$.m = $3; $$.x = $5; $$.y = $7; } ; area : location { $$.ty = AREA::LOCATION; $$.a.a_loc = $1; } | location '@' '+' '(' expr ',' expr ')' { $$.ty = AREA::RECT; $$.a.a_rect.loc = $1; $$.a.a_rect.width = $5; $$.a.a_rect.height = $7; } | location TOWARDS expr ':' '(' expr ',' expr ')' { $$.ty = AREA::BAR; $$.a.a_bar.loc = $1; $$.a.a_bar.width = $6; $$.a.a_bar.depth = $8; $$.a.a_bar.dir = $3; } ; spelldef : spellbody_list { $$ = new_spell($1); } | LET defs IN spellbody_list { $$ = new_spell($4); $$->letdefv = std::move(*$2); $2.delete_(); $$->spellguard = $4; } ; defs : semicolons { $$.new_(); } | defs def semicolons { $$ = $1; $$->push_back($2); } ; def : ID '=' expr { if (find_constant($1.str())) { fail(@1.first_line, @1.first_column, "Attempt to re-define constant `%s' as LET-bound variable.\n", $1.c_str()); } else { $$.id = intern_id($1); $$.expr = $3; } $1.delete_(); } ; spellbody_list : spellbody { $$ = $1; } | spellbody '|' spellbody_list { dumb_ptr sg = new_spellguard(SPELLGUARD::CHOICE); sg->next = $1; sg->s.s_alt = $3; $$ = sg; } ; spellbody : spellguard DARROW spellbody { $$ = spellguard_implication($1, $3); } | '(' spellbody_list ')' { $$ = $2; } | EFFECT_ effect_list maybe_trigger maybe_end { dumb_ptr sg = new_spellguard(SPELLGUARD::EFFECT); sg->s.s_effect.effect = $2; sg->s.s_effect.at_trigger = $3; sg->s.s_effect.at_end = $4; $$ = sg; } ; maybe_trigger : /* empty */ { $$ = NULL; } | ATTRIGGER effect_list { $$ = $2; } ; maybe_end : /* empty */ { $$ = NULL; } | ATEND effect_list { $$ = $2; } ; spellguard : prereq { $$ = $1; } | spellguard OR spellguard { dumb_ptr sg = new_spellguard(SPELLGUARD::CHOICE); sg->next = $1; sg->s.s_alt = $3; $$ = sg; } | '(' spellguard_list ')' { $$ = $2; } ; spellguard_list : spellguard { $$ = $1; } | spellguard ',' spellguard_list { $$ = spellguard_implication($1, $3); } ; prereq : REQUIRE expr { $$ = new_spellguard(SPELLGUARD::CONDITION); $$->s.s_condition = $2; } | CATALYSTS items { $$ = new_spellguard(SPELLGUARD::CATALYSTS); $$->s.s_catalysts = $2; } | COMPONENTS items { $$ = new_spellguard(SPELLGUARD::COMPONENTS); $$->s.s_components = $2; } | MANA expr { $$ = new_spellguard(SPELLGUARD::MANA); $$->s.s_mana = $2; } | CASTTIME expr { $$ = new_spellguard(SPELLGUARD::CASTTIME); $$->s.s_casttime = $2; } ; items : '[' item_list ']' { $$ = $2; } ; item_list : item { $$ = NULL; magic_add_component(&$$, $1.id, $1.count); } | item_list ',' item { $$ = $1; magic_add_component(&$$, $3.id, $3.count); } ; item : INT '*' item_name { $$.id = $3; $$.count = $1; } | item_name { $$.id = $1; $$.count = 1; } ; item_name : STRING { struct item_data *item = itemdb_searchname(stringish(ZString($1))); if (!item) { fail(@1.first_line, @1.first_column, "Unknown item `%s'\n", $1.c_str()); $$ = 0; } else $$ = item->nameid; $1.delete_(); } | INT { $$ = $1; } ; selection : PC_F { $$ = FOREACH_FILTER::PC; } | MOB_F { $$ = FOREACH_FILTER::MOB; } | ENTITY_F { $$ = FOREACH_FILTER::ENTITY; } | SPELL { $$ = FOREACH_FILTER::SPELL; } | TARGET_F { $$ = FOREACH_FILTER::TARGET; } | NPC_F { $$ = FOREACH_FILTER::NPC; } ; effect : '(' effect_list ')' { $$ = $2; } | SKIP ';' { $$ = new_effect(EFFECT::SKIP); } | ABORT ';' { $$ = new_effect(EFFECT::ABORT); } | END ';' { $$ = new_effect(EFFECT::END); } | BREAK ';' { $$ = new_effect(EFFECT::BREAK); } | ID '=' expr ';' { if (find_constant($1.str())) { fail(@1.first_line, @1.first_column, "Attempt to re-define constant `%s' in assignment.", $1.c_str()); } else { $$ = new_effect(EFFECT::ASSIGN); $$->e.e_assign.id = intern_id($1); $$->e.e_assign.expr = $3; } $1.delete_(); } | FOREACH selection ID IN expr DO effect { $$ = new_effect(EFFECT::FOREACH); $$->e.e_foreach.id = intern_id($3); $3.delete_(); $$->e.e_foreach.area = $5; $$->e.e_foreach.body = $7; $$->e.e_foreach.filter = $2; } | FOR ID '=' expr TO expr DO effect { $$ = new_effect(EFFECT::FOR); $$->e.e_for.id = intern_id($2); $2.delete_(); $$->e.e_for.start = $4; $$->e.e_for.stop = $6; $$->e.e_for.body = $8; } | IF expr THEN effect ELSE effect { $$ = new_effect(EFFECT::IF); $$->e.e_if.cond = $2; $$->e.e_if.true_branch = $4; $$->e.e_if.false_branch = $6; } | IF expr THEN effect { $$ = new_effect(EFFECT::IF); $$->e.e_if.cond = $2; $$->e.e_if.true_branch = $4; $$->e.e_if.false_branch = new_effect(EFFECT::SKIP); } | SLEEP expr ';' { $$ = new_effect(EFFECT::SLEEP); $$->e.e_sleep = $2; } | ID '(' arg_list ')' ';' { $$ = op_effect($1.str(), *$3.argvp, @1.first_line, @1.first_column); $1.delete_(); } | SCRIPT_DATA { $$ = new_effect(EFFECT::SCRIPT); $$->e.e_script = dumb_ptr(parse_script(ZString($1), @1.first_line).release()); $1.delete_(); if ($$->e.e_script == NULL) fail(@1.first_line, @1.first_column, "Failed to compile script\n"); } | CALL ID '(' arg_list ')' ';' { $$ = call_proc($2, $4.argvp, @1.first_line, @1.first_column); $2.delete_(); } ; effect_list : /* empty */ { $$ = new_effect(EFFECT::SKIP); } | effect semicolons effect_list { $$ = set_effect_continuation($1, $3); } ; %% size_t intern_id(ZString id_name) { size_t i; for (i = 0; i < magic_conf.varv.size(); i++) if (id_name == magic_conf.varv[i].name) return i; // i = magic_conf.varv.size(); /* Must add new */ magic_conf_t::mcvar new_var {}; new_var.name = id_name; new_var.val.ty = TYPE::UNDEF; magic_conf.varv.push_back(new_var); return i; } void add_spell(dumb_ptr spell, int line_nr) { auto pair1 = magic_conf.spells_by_name.insert({spell->name, spell}); if (!pair1.second) { fail(line_nr, 0, "Attempt to redefine spell `%s'\n", spell->name.c_str()); return; } auto pair2 = magic_conf.spells_by_invocation.insert({spell->invocation, spell}); if (!pair2.second) { fail(line_nr, 0, "Attempt to redefine spell invocation `%s' between spells `%s' and `%s'\n", spell->invocation.c_str(), pair1.first->second->name.c_str(), spell->name.c_str()); magic_conf.spells_by_name.erase(pair1.first); return; } } void add_teleport_anchor(dumb_ptr anchor, int line_nr) { auto pair1 = magic_conf.anchors_by_name.insert({anchor->name, anchor}); if (!pair1.second) { fail(line_nr, 0, "Attempt to redefine teleport anchor `%s'\n", anchor->name.c_str()); return; } auto pair2 = magic_conf.anchors_by_invocation.insert({anchor->name, anchor}); if (!pair2.second) { fail(line_nr, 0, "Attempt to redefine anchor invocation `%s' between anchors `%s' and `%s'\n", anchor->invocation.c_str(), pair1.first->second->name.c_str(), anchor->name.c_str()); magic_conf.anchors_by_name.erase(pair1.first); return; } } void fail(int line, int column, const char *fmt, ...) { va_list ap; FPRINTF(stderr, "[magic-init] L%d:%d: ", line, column); va_start(ap, fmt); vfprintf(stderr, fmt, ap); failed_flag = 1; } dumb_ptr dot_expr(dumb_ptr expr, int id) { dumb_ptr retval = magic_new_expr(EXPR::SPELLFIELD); retval->e.e_field.id = id; retval->e.e_field.expr = expr; return retval; } dumb_ptr fun_expr(FString name, const_array> argv, int line, int column) { dumb_ptr expr; fun_t *fun = magic_get_fun(name); if (!fun) fail(line, column, "Unknown function `%s'\n", name.c_str()); else if (fun->signature.size() != argv.size()) { fail(line, column, "Incorrect number of arguments to function `%s': Expected %zu, found %zu\n", name.c_str(), fun->signature.size(), argv.size()); fun = NULL; } if (fun) { expr = magic_new_expr(EXPR::FUNAPP); expr->e.e_funapp.line_nr = line; expr->e.e_funapp.column = column; expr->e.e_funapp.funp = fun; assert (argv.size() <= MAX_ARGS); expr->e.e_funapp.args_nr = argv.size(); std::copy(argv.begin(), argv.end(), expr->e.e_funapp.args); } else { /* failure */ expr = magic_new_expr(EXPR::VAL); expr->e.e_val.ty = TYPE::FAIL; } return expr; } dumb_ptr new_spell(dumb_ptr guard) { auto retval = dumb_ptr::make(); retval->spellguard = guard; return retval; } dumb_ptr new_spellguard(SPELLGUARD ty) { dumb_ptr retval = dumb_ptr::make(); retval->ty = ty; return retval; } dumb_ptr spellguard_implication(dumb_ptr a, dumb_ptr b) { dumb_ptr retval = a; if (a == b) { /* This can happen due to reference sharing: * e.g., * (R0 -> (R1 | R2)) => (R3) * yields * (R0 -> (R1 -> R3 | R2 -> R3)) * * So if we now add => R4 to that, we want * (R0 -> (R1 -> R3 -> R4 | R2 -> R3 -> R4)) * * but we only need to add it once, because the R3 reference is shared. */ return retval; } /* If the premise is a disjunction, b is the continuation of _all_ branches */ if (a->ty == SPELLGUARD::CHOICE) spellguard_implication(a->s.s_alt, b); if (a->next) spellguard_implication(a->next, b); else a->next = b; return retval; } dumb_ptr new_effect(EFFECT ty) { auto effect = dumb_ptr::make(); effect->ty = ty; return effect; } dumb_ptr set_effect_continuation(dumb_ptr src, dumb_ptr continuation) { dumb_ptr retval = src; /* This function is completely analogous to `spellguard_implication' above; read the control flow implications above first before pondering it. */ if (src == continuation) return retval; /* For FOR and FOREACH, we use special stack handlers and thus don't have to set * the continuation. It's only IF that we need to handle in this fashion. */ if (src->ty == EFFECT::IF) { set_effect_continuation(src->e.e_if.true_branch, continuation); set_effect_continuation(src->e.e_if.false_branch, continuation); } if (src->next) set_effect_continuation(src->next, continuation); else src->next = continuation; return retval; } dumb_ptr op_effect(FString name, const_array> argv, int line, int column) { dumb_ptr effect; op_t *op = magic_get_op(name); if (!op) fail(line, column, "Unknown operation `%s'\n", name.c_str()); else if (op->signature.size() != argv.size()) { fail(line, column, "Incorrect number of arguments to operation `%s': Expected %zu, found %zu\n", name.c_str(), op->signature.size(), argv.size()); op = NULL; } if (op) { effect = new_effect(EFFECT::OP); effect->e.e_op.line_nr = line; effect->e.e_op.column = column; effect->e.e_op.opp = op; assert (argv.size() <= MAX_ARGS); effect->e.e_op.args_nr = argv.size(); std::copy(argv.begin(), argv.end(), effect->e.e_op.args); } else /* failure */ effect = new_effect(EFFECT::SKIP); return effect; } std::map procs; // I think this was a memory leak (or undefined behavior) void install_proc(dumb_ptr proc) { procs.insert({proc->name, std::move(*proc)}); } dumb_ptr call_proc(ZString name, dumb_ptr>> argvp, int line_nr, int column) { auto pi = procs.find(name); if (pi == procs.end()) { fail(line_nr, column, "Unknown procedure `%s'\n", name.c_str()); return new_effect(EFFECT::SKIP); } proc_t *p = &pi->second; if (p->argv.size() != argvp->size()) { fail(line_nr, column, "Procedure %s/%zu invoked with %zu parameters\n", name.c_str(), p->argv.size(), argvp->size()); return new_effect(EFFECT::SKIP); } dumb_ptr retval = new_effect(EFFECT::CALL); retval->e.e_call.body = p->body; retval->e.e_call.formalv = &p->argv; retval->e.e_call.actualvp = argvp; return retval; } std::map const_defm; void bind_constant(FString name, val_t *val, int line_nr) { if (!const_defm.insert({name, *val}).second) { fail(line_nr, 0, "Redefinition of constant `%s'\n", name.c_str()); } } val_t *find_constant(FString name) { auto it = const_defm.find(name); if (it != const_defm.end()) return &it->second; return NULL; } static int error_flag; inline void INTERN_ASSERT(ZString name, int id) { int zid = intern_id(name); if (zid != id) FPRINTF(stderr, "[magic-conf] INTERNAL ERROR: Builtin special var %s interned to %d, not %d as it should be!\n", name, zid, id); error_flag = 1; } extern FILE *magic_frontend_in; // must be called after itemdb initialisation int magic_init(const char *conffile) { error_flag = 0; INTERN_ASSERT("min_casttime", VAR_MIN_CASTTIME); INTERN_ASSERT("obscure_chance", VAR_OBSCURE_CHANCE); INTERN_ASSERT("caster", VAR_CASTER); INTERN_ASSERT("spellpower", VAR_SPELLPOWER); INTERN_ASSERT("self_spell", VAR_SPELL); INTERN_ASSERT("self_invocation", VAR_INVOCATION); INTERN_ASSERT("target", VAR_TARGET); INTERN_ASSERT("script_target", VAR_SCRIPTTARGET); INTERN_ASSERT("location", VAR_LOCATION); magic_frontend_in = fopen(conffile, "r"); if (!magic_frontend_in) { FPRINTF(stderr, "[magic-conf] Magic configuration file `%s' not found -> no magic.\n", conffile); return 0; } magic_frontend_parse(); PRINTF("[magic-conf] Magic initialised. %zu spells, %zu teleport anchors.\n", magic_conf.spells_by_name.size(), magic_conf.anchors_by_name.size()); return error_flag; } extern int magic_frontend_lineno; void magic_frontend_error(const char *msg) { FPRINTF(stderr, "[magic-conf] Parse error: %s at line %d\n", msg, magic_frontend_lineno); failed_flag = 1; }