%code requires
{
#include "magic-interpreter.hpp"
} // %code requires
%code
{
#include "magic-interpreter-parser.hpp"
#include <cassert>
#include <cstdarg> // exception to "no va_list" rule, even after cxxstdio
#include "../strings/fstring.hpp"
#include "../strings/zstring.hpp"
#include "../io/cxxstdio.hpp"
#include "../common/const_array.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<expr_t> fun_expr(FString name, const_array<dumb_ptr<expr_t>> argv, int line, int column);
static
dumb_ptr<expr_t> dot_expr(dumb_ptr<expr_t> lhs, int id);
static
void BIN_EXPR(dumb_ptr<expr_t>& x, FString name, dumb_ptr<expr_t> arg1, dumb_ptr<expr_t> arg2, int line, int column)
{
dumb_ptr<expr_t> e[2];
e[0] = arg1;
e[1] = arg2;
x = fun_expr(name, const_array<dumb_ptr<expr_t>>(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<spell_t> new_spell(dumb_ptr<spellguard_t> guard);
static
dumb_ptr<spellguard_t> spellguard_implication(dumb_ptr<spellguard_t> a, dumb_ptr<spellguard_t> b);
static
dumb_ptr<spellguard_t> new_spellguard(SPELLGUARD ty);
static
dumb_ptr<effect_t> new_effect(EFFECT ty);
static
dumb_ptr<effect_t> set_effect_continuation(dumb_ptr<effect_t> src, dumb_ptr<effect_t> continuation);
static
void add_spell(dumb_ptr<spell_t> spell, int line_nr);
static
void add_teleport_anchor(dumb_ptr<teleport_anchor_t> anchor, int line_nr);
static
dumb_ptr<effect_t> op_effect(FString name, const_array<dumb_ptr<expr_t>> argv, int line, int column);
// in magic-interpreter-lexer.cpp
int magic_frontend_lex(YYSTYPE *, YYLTYPE *);
static
void install_proc(dumb_ptr<proc_t> proc);
static
dumb_ptr<effect_t> call_proc(ZString name, dumb_ptr<std::vector<dumb_ptr<expr_t>>> 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_t> expr;
e_location_t location;
e_area_t area;
args_rec_t arg_list;
dumb_ptr<std::vector<letdef_t>> letdefvp;
dumb_ptr<spell_t> spell;
struct { int id; SPELLARG ty; } spellarg_def;
letdef_t vardef;
dumb_ptr<spellguard_t> spellguard;
dumb_ptr<component_t> components;
struct { int id, count; } component;
dumb_ptr<effect_t> effect;
dumb_ptr<proc_t> 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 <i> INT
%token <s> STRING
%token <s> ID
%token <i> 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 <s> 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> value
%type <location> location
%type <area> area
%type <arg_list> arg_list
%type <arg_list> arg_list_ne
%type <letdefvp> defs
%type <spell> spelldef
%type <spellarg_def> argopt
%type <vardef> def
%type <spellguard> spellbody_list
%type <spellguard> spellbody
%type <spellguard> spellguard
%type <spellguard> spellguard_list
%type <spellguard> prereq
%type <component> item
%type <components> items
%type <components> item_list
%type <i> item_name
%type <foreach_filter> selection;
%type <effect> effect
%type <effect> effect_list
%type <effect> maybe_trigger
%type <effect> maybe_end
%type <spell_flags> spell_flags;
%type <expr> expr
%type <spell_arg> arg_ty
%type <proc> proc_formals_list
%type <proc> 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<proc_t>::make();
}
| proc_formals_list_ne
{
$$ = $1;
}
;
proc_formals_list_ne
: ID
{
$$ = dumb_ptr<proc_t>::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<env_t>(&magic_default_env), &magic_conf.varv[var_id].val, $3);
}
$1.delete_();
}
| CONST ID '=' expr
{
val_t var;
magic_eval(dumb_ptr<env_t>(&magic_default_env), &var, $4);
bind_constant($2.str(), &var, @1.first_line);
$2.delete_();
}
| TELEPORT_ANCHOR ID ':' expr '=' expr
{
auto anchor = dumb_ptr<teleport_anchor_t>::make();
anchor->name = $2.str();
$2.delete_();
anchor->invocation = magic_eval_str(dumb_ptr<env_t>(&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_t> 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_t> spell = $8;
spell->name = $3.str();
$3.delete_();
spell->invocation = magic_eval_str(dumb_ptr<env_t>(&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<dumb_ptr<expr_t>>(&$$, 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<spellguard_t> 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<spellguard_t> 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<spellguard_t> 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<ItemName>(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<const ScriptBuffer>(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_t> 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<teleport_anchor_t> 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<expr_t> dot_expr(dumb_ptr<expr_t> expr, int id)
{
dumb_ptr<expr_t> retval = magic_new_expr(EXPR::SPELLFIELD);
retval->e.e_field.id = id;
retval->e.e_field.expr = expr;
return retval;
}
dumb_ptr<expr_t> fun_expr(FString name, const_array<dumb_ptr<expr_t>> argv, int line, int column)
{
dumb_ptr<expr_t> 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<spell_t> new_spell(dumb_ptr<spellguard_t> guard)
{
auto retval = dumb_ptr<spell_t>::make();
retval->spellguard = guard;
return retval;
}
dumb_ptr<spellguard_t> new_spellguard(SPELLGUARD ty)
{
dumb_ptr<spellguard_t> retval = dumb_ptr<spellguard_t>::make();
retval->ty = ty;
return retval;
}
dumb_ptr<spellguard_t> spellguard_implication(dumb_ptr<spellguard_t> a, dumb_ptr<spellguard_t> b)
{
dumb_ptr<spellguard_t> 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<effect_t> new_effect(EFFECT ty)
{
auto effect = dumb_ptr<effect_t>::make();
effect->ty = ty;
return effect;
}
dumb_ptr<effect_t> set_effect_continuation(dumb_ptr<effect_t> src, dumb_ptr<effect_t> continuation)
{
dumb_ptr<effect_t> 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<effect_t> op_effect(FString name, const_array<dumb_ptr<expr_t>> argv, int line, int column)
{
dumb_ptr<effect_t> 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<FString, proc_t> procs;
// I think this was a memory leak (or undefined behavior)
void install_proc(dumb_ptr<proc_t> proc)
{
procs.insert({proc->name, std::move(*proc)});
}
dumb_ptr<effect_t> call_proc(ZString name, dumb_ptr<std::vector<dumb_ptr<expr_t>>> 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<effect_t> 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<FString, val_t> 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;
}