%code requires {
#include "magic-interpreter.hpp"
}
%code{
#include "magic-interpreter-parser.hpp"
#include <cstdarg> // exception to "no va_list" rule, even after cxxstdio
#include "../common/cxxstdio.hpp"
#include "itemdb.hpp"
#include "magic-expr.hpp"
#define YYLEX_PARAM 0, 0
static
int intern_id(const char *id_name);
static
expr_t *fun_expr(const char *name, int args_nr, expr_t **args, int line, int column);
static
expr_t *dot_expr(expr_t *lhs, int id);
#define BIN_EXPR(x, name, arg1, arg2, line, column) { expr_t *e[2]; e[0] = arg1; e[1] = arg2; x = fun_expr(name, 2, e, line, column); }
static
int failed_flag = 0;
static
void magic_frontend_error(const char *msg);
static
void fail(int line, int column, const char *fmt, ...);
static
spell_t *new_spell(spellguard_t *guard);
static
spellguard_t *spellguard_implication(spellguard_t *a, spellguard_t *b);
static
spellguard_t *new_spellguard(SPELLGUARD ty);
static
effect_t *new_effect(EFFECT_ ty);
static
effect_t *set_effect_continuation(effect_t *src, effect_t *continuation);
static
void add_spell(spell_t *spell, int line_nr);
static
void add_teleport_anchor(teleport_anchor_t *anchor, int line_nr);
static
effect_t *op_effect(char *name, int args_nr, expr_t **args, int line, int column);
// in magic-interpreter-lexer.cpp
int magic_frontend_lex(YYSTYPE *, YYLTYPE *);
static
void install_proc(proc_t *proc);
static
effect_t *call_proc(char *name, int args_nr, expr_t **args, int line_nr, int column);
static
void bind_constant(char *name, val_t *val, int line_nr);
static
val_t *find_constant(char *name);
}
%name-prefix="magic_frontend_"
%locations
%union {
int i;
SPELL_FLAG spell_flags;
SPELLARG spell_arg;
FOREACH_FILTER foreach_filter;
char *s;
int op;
magic_conf_t *magic_conf;
val_t value;
expr_t *expr;
e_location_t location;
e_area_t area;
args_rec_t arg_list;
struct { int letdefs_nr; letdef_t *letdefs; } letdefs;
spell_t *spell;
struct { int id; SPELLARG ty; } spellarg_def;
letdef_t vardef;
spellguard_t *spellguard;
component_t *components;
struct {int id, count; } component;
effect_t *effect;
proc_t *proc;
};
%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 <letdefs> 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 */
{ CREATE($$, proc_t, 1); }
| proc_formals_list_ne
{ $$ = $1; }
;
proc_formals_list_ne : ID
{ CREATE($$, proc_t, 1);
$$->args_nr = 1;
$$->args = (int*)malloc(sizeof(int));
$$->args[0] = intern_id($1);
}
| proc_formals_list_ne ',' ID
{ $$ = $1;
$$->args = (int*)realloc($$->args, sizeof(int) * (1 + $$->args_nr));
$$->args[$$->args_nr++] = intern_id($3);
}
;
spellconf_option : ID '=' expr
{
int var_id;
if (find_constant($1)) {
fail(@1.first_line, 0, "Attempt to redefine constant `%s' as global\n", $1);
free($1);
} else {
var_id = intern_id($1);
magic_eval(&magic_default_env, &magic_conf.vars[var_id], $3);
}
}
| CONST ID '=' expr
{
val_t var;
magic_eval(&magic_default_env, &var, $4);
bind_constant($2, &var, @1.first_line);
}
| TELEPORT_ANCHOR ID ':' expr '=' expr
{
teleport_anchor_t *anchor;
CREATE(anchor, teleport_anchor_t, 1);
anchor->name = $2;
anchor->invocation = magic_eval_str(&magic_default_env, $4);
anchor->location = $6;
if (!failed_flag)
add_teleport_anchor(anchor, @1.first_line);
else
free(anchor);
failed_flag = 0;
}
| PROCEDURE ID '(' proc_formals_list ')' '=' effect_list
{
proc_t *proc = $4;
proc->name = $2;
proc->body = $7;
if (!failed_flag)
install_proc(proc);
failed_flag = 0;
}
| spell_flags SPELL ID argopt ':' expr '=' spelldef
{ spell_t *spell = $8;
spell->name = $3;
spell->invocation = magic_eval_str(&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);
$$.ty = $4; }
;
arg_ty : PC_F
{ $$ = SPELLARG_PC; }
| STRING_TY
{ $$ = SPELLARG_STRING; }
;
value : DIR
{ $$.ty = TY_DIR;
$$.v.v_int = $1; }
| INT
{ $$.ty = TY_INT;
$$.v.v_int = $1; }
| STRING
{ $$.ty = TY_STRING;
$$.v.v_string = $1; }
;
expr : value
{ $$ = magic_new_expr(EXPR_VAL);
$$->e.e_val = $1; }
| ID
{
val_t *val;
if ((val = find_constant($1))) {
$$ = magic_new_expr(EXPR_VAL);
$$->e.e_val = *val;
} else {
$$ = magic_new_expr(EXPR_ID);
$$->e.e_id = intern_id($1);
}
}
| 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", 1, &$$, @1.first_line, @1.first_column); }
| ID '(' arg_list ')'
{ $$ = fun_expr($1, $3.args_nr, $3.args, @1.first_line, @1.first_column);
if ($3.args)
free($3.args);
free($1); }
| '(' expr ')'
{ $$ = $2; }
| expr '.' ID
{ $$ = dot_expr($1, intern_id($3)); }
;
arg_list : /* empty */
{ $$.args_nr = 0; }
| arg_list_ne
{ $$ = $1; }
;
arg_list_ne : expr
{ CREATE($$.args, expr_t *, 1);
$$.args_nr = 1;
$$.args[0] = $1;
}
| arg_list_ne ',' expr
{ RECREATE($$.args, expr_t *, 1 + $$.args_nr);
$$.args[$$.args_nr++] = $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);
$$->letdefs_nr = $2.letdefs_nr;
$$->letdefs = $2.letdefs;
$$->spellguard = $4;
}
;
defs : semicolons
{ $$.letdefs_nr = 0;
CREATE($$.letdefs, letdef_t, 1);
}
| defs def semicolons
{ $$ = $1;
$$.letdefs_nr++;
RECREATE($$.letdefs, letdef_t, $$.letdefs_nr);
$$.letdefs[$1.letdefs_nr] = $2;
}
;
def : ID '=' expr
{
if (find_constant($1)) {
fail(@1.first_line, @1.first_column, "Attempt to re-define constant `%s' as LET-bound variable.\n", $1);
free($1);
} else {
$$.id = intern_id($1);
$$.expr = $3;
}
}
;
spellbody_list : spellbody
{ $$ = $1; }
| spellbody '|' spellbody_list
{ 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
{ 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
{ 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($1);
if (!item) {
fail(@1.first_line, @1.first_column, "Unknown item `%s'\n", $1);
$$ = 0;
} else
$$ = item->nameid;
free($1);
}
| 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)) {
fail(@1.first_line, @1.first_column, "Attempt to re-define constant `%s' in assignment.", $1);
free($1);
} else {
$$ = new_effect(EFFECT_ASSIGN);
$$->e.e_assign.id = intern_id($1);
$$->e.e_assign.expr = $3;
}
}
| FOREACH selection ID IN expr DO effect
{ $$ = new_effect(EFFECT_FOREACH);
$$->e.e_foreach.id = intern_id($3);
$$->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);
$$->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, $3.args_nr, $3.args, @1.first_line, @1.first_column);
free($1);
}
| SCRIPT_DATA
{ $$ = new_effect(EFFECT_SCRIPT);
$$->e.e_script = parse_script($1, @1.first_line);
free($1);
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.args_nr, $4.args, @1.first_line, @1.first_column);
free($2);
}
;
effect_list : /* empty */
{ $$ = new_effect(EFFECT_SKIP); }
| effect semicolons effect_list
{ $$ = set_effect_continuation($1, $3); }
;
%%
/* We do incremental realloc here to store our results. Since this happens only once
* during startup for a relatively manageable set of configs, it should be fine. */
static
int intern_id(const char *id_name)
{
int i;
for (i = 0; i < magic_conf.vars_nr; i++)
if (!strcmp(id_name, magic_conf.var_name[i])) {
free((char*)id_name);
return i;
}
/* Must add new */
i = magic_conf.vars_nr++;
RECREATE(magic_conf.var_name, const char *, magic_conf.vars_nr);
magic_conf.var_name[i] = id_name;
RECREATE(magic_conf.vars, val_t, magic_conf.vars_nr);
magic_conf.vars[i].ty = TY_UNDEF;
return i;
}
static
void add_spell(spell_t *spell, int line_nr)
{
int index = magic_conf.spells_nr;
int i;
for (i = 0; i < index; i++) {
if (!strcmp(magic_conf.spells[i]->name, spell->name)) {
fail(line_nr, 0, "Attempt to redefine spell `%s'\n", spell->name);
return;
}
if (!strcmp(magic_conf.spells[i]->invocation, spell->invocation)) {
fail(line_nr, 0, "Attempt to redefine spell invocation `%s' between spells `%s' and `%s'\n",
spell->invocation, magic_conf.spells[i]->name, spell->name);
return;
}
}
magic_conf.spells_nr++;
RECREATE(magic_conf.spells, spell_t *, magic_conf.spells_nr);
magic_conf.spells[index] = spell;
}
static
void add_teleport_anchor(teleport_anchor_t *anchor, int line_nr)
{
int index = magic_conf.anchors_nr;
int i;
for (i = 0; i < index; i++) {
if (!strcmp(magic_conf.anchors[i]->name, anchor->name)) {
fail(line_nr, 0, "Attempt to redefine teleport anchor `%s'\n", anchor->name);
return;
}
if (!strcmp(magic_conf.anchors[i]->invocation, anchor->invocation)) {
fail(line_nr, 0, "Attempt to redefine anchor invocation `%s' between anchors `%s' and `%s'\n",
anchor->invocation, magic_conf.anchors[i]->name, anchor->name);
return;
}
}
magic_conf.anchors_nr++;
RECREATE(magic_conf.anchors, teleport_anchor_t *, magic_conf.anchors_nr);
magic_conf.anchors[index] = anchor;
}
static __attribute__((format(printf, 3, 4)))
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;
}
static
expr_t *dot_expr(expr_t *expr, int id)
{
expr_t *retval = magic_new_expr(EXPR_SPELLFIELD);
retval->e.e_field.id = id;
retval->e.e_field.expr = expr;
return retval;
}
static
expr_t *fun_expr(const char *name, int args_nr, expr_t **args, int line, int column)
{
int id;
expr_t *expr;
fun_t *fun = magic_get_fun(name, &id);
if (!fun) {
fail(line, column, "Unknown function `%s'\n", name);
} else if (strlen(fun->signature) != args_nr) {
fail(line, column, "Incorrect number of arguments to function `%s': Expected %d, found %d\n", name, strlen(fun->signature), args_nr);
fun = NULL;
}
if (fun) {
int i;
expr = magic_new_expr(EXPR_FUNAPP);
expr->e.e_funapp.line_nr = line;
expr->e.e_funapp.column = column;
expr->e.e_funapp.id = id;
expr->e.e_funapp.args_nr = args_nr;
for (i = 0; i < args_nr; i++)
expr->e.e_funapp.args[i] = args[i];
} else { /* failure */
expr = magic_new_expr(EXPR_VAL);
expr->e.e_val.ty = TY_FAIL;
}
return expr;
}
static
spell_t *new_spell(spellguard_t *guard)
{
static int spell_counter = 0;
spell_t *retval = (spell_t*)calloc(1, sizeof(spell_t));
retval->index = ++spell_counter;
retval->spellguard = guard;
return retval;
}
static
spellguard_t *new_spellguard(SPELLGUARD ty)
{
spellguard_t *retval = (spellguard_t *)calloc(1, sizeof(spellguard_t));
retval->ty = ty;
return retval;
}
static
spellguard_t *spellguard_implication(spellguard_t *a, spellguard_t *b)
{
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;
}
static
effect_t *new_effect(EFFECT_ ty)
{
effect_t *effect = (effect_t *) calloc(1, sizeof(effect_t));
effect->ty = ty;
return effect;
}
static
effect_t *set_effect_continuation(effect_t *src, effect_t *continuation)
{
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;
}
static
effect_t *op_effect(char *name, int args_nr, expr_t **args, int line, int column)
{
int id;
effect_t *effect;
op_t *op = magic_get_op(name, &id);
if (!op)
fail(line, column, "Unknown operation `%s'\n", name);
else if (strlen(op->signature) != args_nr) {
fail(line, column, "Incorrect number of arguments to operation `%s': Expected %d, found %d\n", name, strlen(op->signature), args_nr);
op = NULL;
}
if (op) {
int i;
effect = new_effect(EFFECT_OP);
effect->e.e_op.line_nr = line;
effect->e.e_op.column = column;
effect->e.e_op.id = id;
effect->e.e_op.args_nr = args_nr;
for (i = 0; i < args_nr; i++)
effect->e.e_op.args[i] = args[i];
} else /* failure */
effect = new_effect(EFFECT_SKIP);
return effect;
}
proc_t *procs = NULL;
int procs_nr = 0;
// I think this is a memory leak, or undefined behavior
static
void install_proc(proc_t *proc)
{
if (!procs) {
procs = proc;
procs_nr = 1;
} else {
RECREATE(procs, proc_t, 1 + procs_nr);
procs[procs_nr++] = *proc;
}
}
static
effect_t *call_proc(char *name, int args_nr, expr_t **args, int line_nr, int column)
{
proc_t *p = NULL;
int i;
effect_t *retval;
for (i = 0; i < procs_nr; i++)
if (!strcmp(procs[i].name, name)) {
p = &procs[i];
break;
}
if (!p) {
fail(line_nr, column, "Unknown procedure `%s'\n", name);
return new_effect(EFFECT_SKIP);
}
if (p->args_nr != args_nr) {
fail(line_nr, column, "Procedure %s/%d invoked with %d parameters\n", name, p->args_nr, args_nr);
return new_effect(EFFECT_SKIP);
}
retval = new_effect(EFFECT_CALL);
retval->e.e_call.body = p->body;
retval->e.e_call.args_nr = args_nr;
retval->e.e_call.formals = p->args;
retval->e.e_call.actuals = args;
return retval;
}
struct const_def_rec {
char *name;
val_t val;
} *const_defs = NULL;
int const_defs_nr = 0;
static
void bind_constant(char *name, val_t *val, int line_nr)
{
if (find_constant(name)) {
fail(line_nr, 0, "Redefinition of constant `%s'\n", name);
return;
}
if (!const_defs)
const_defs = (struct const_def_rec *)malloc(sizeof(struct const_def_rec));
else
const_defs = (struct const_def_rec *)realloc(const_defs,
(const_defs_nr + 1) * sizeof(struct const_def_rec));
const_defs[const_defs_nr].name = name;
const_defs[const_defs_nr].val = *val;
++const_defs_nr;
}
static
val_t *find_constant(char *name)
{
int i;
for (i = 0; i < const_defs_nr; i++) {
if (!strcmp(const_defs[i].name, name)) {
free(name);
return &const_defs[i].val;
}
}
return NULL;
}
#define INTERN_ASSERT(name, 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)
{
int error_flag = 0;
magic_conf.vars_nr = 0;
// can these be left NULL ? I'm afraid to change anything.
magic_conf.var_name = (const char **)malloc(1);
magic_conf.vars = (val_t *)malloc(1);
magic_conf.obscure_chance = 95;
magic_conf.min_casttime = 100;
magic_conf.spells_nr = 0;
CREATE(magic_conf.spells, spell_t *, 1);
magic_conf.anchors_nr = 0;
CREATE(magic_conf.anchors, teleport_anchor_t *, 1);
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();
if (magic_conf.vars[VAR_MIN_CASTTIME].ty == TY_INT)
magic_conf.min_casttime = magic_conf.vars[VAR_MIN_CASTTIME].v.v_int;
if (magic_conf.vars[VAR_OBSCURE_CHANCE].ty == TY_INT)
magic_conf.obscure_chance = magic_conf.vars[VAR_OBSCURE_CHANCE].v.v_int;
PRINTF("[magic-conf] Magic initialised; obscure at %d%%. %d spells, %d teleport anchors.\n",
magic_conf.obscure_chance, magic_conf.spells_nr, magic_conf.anchors_nr);
if (procs)
free(procs);
return error_flag;
}
extern int magic_frontend_lineno;
static
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;
}