summaryrefslogblamecommitdiff
path: root/src/map/magic-interpreter-parser.ypp
blob: f067a8bf2797309a7b867703ff1032fc2a5809c4 (plain) (tree)
1
2
3
4
5
6
7
8
9

              
                                



                   

                                       
                  

                                                                         


                                 

                             
                                    
 

                         
 
                                           
                        
 
                                               


                                         
                 
                                                              
      
 
      
                                  
 
      
                                                                                                  
 
      
                                                        
 
      
                                                                                                                    
 
                          

                
                                                                          
 
 

                    
 

                                           
 
                                            
                                                      
 
      
                                                          
 
      
                                                                                                  
 
      
                                                     
 
      
                                         
 
      
                                                                                                    
 
      
                                                     
 
      
                                                                          
 
      
                                                                                                     
 
                                 
                                             
 
      
                                         
 
      
                                                                                                                   
 
      
                                                          
 
      
                                   
 
          




                              

      
          


                                  
                  
           
                                
                
                          


                          

                                             
                                                 
                    






                                        
                                            


                                                       
           

















                 
          












                
               

























                      
              


                
            















                            
                     











                                  
                                 



                            
                                

                 
                        











                                 
         


  

























                                       
                                  













                      


                                      




                             

                                      








                
                                
     
                                                                                            


        

                                                                                          
     
                 




                   


                                                              



                                      




                                                                                 
 




                                                   



                                                        







                               



                                                   



                                                                                



















































                                                                                    
                 





















































                                   

                                         








                                       
                 




































































































                                                               
                                                                                                



                     


                                                                       









                                     
                 








             
                    













             

                            



                      


                                                                         






















































                                                 

                                 









                        
              




                     
                      








             
                                
     
                                                                                                                        





                              
                 













                              
                                                                   





















                                             
                                                                   















































                                   
                                                                   














































































































                                                
                                                                                 

              
                                                                                



                          
                 









































































                                   
                                
     
                                                                                                              






                                          
                 





                                        
                 








                                   
                 




























                                                       

                                                                        




                                    
                                                                                                      
                 





                                                                           

                                                                 

















                                         
 


  
                                 
 


                                                
                     
 
                                  
                      



                                       
 
             

 
                                                    
 

                                                                        
     

                                                                                  
     
 







                                                                                                    

 
                                                                         
 

                                                                           
     

                                                                                             
     
 







                                                                                                      


 
                                                     
 




                                                            

 
                                                        
 
                                                               

                                  
 
                  

 
                                                                                                 
 
                          
                                     

             
                                                                    
                                                  
     
                                                                                                       
                                                                  




                   


                                            
                                    
 
                                         


                                                                   








                                         

 
                                                         
 
                                            

                               

 
                                                    
 
                                                                   

                    

 
                                                                                                 
 
                                      















                                                                               
 






                                                                                  
 
                  

 
                                        
 
                                             

                    

 
                                                                                                   
 
                                    
                                                                                                                                                      
 
                            
                      













                                                                                    

 
                                                                                                    
 
                              
                                  

            
                                                                     
                                                 
     
                                                                                                        
                                                                 




                  


                                        
                                
                                         
                                             
 
                                                                 


                                          
 
                  


 
                                
 

                                                         
 
                                                 

 
                                                                                                                  
 

                               
     
                                                                        


                                        


                                        
     

                                                                               


                                        
                                                         
                                    

                                        
                  

 
                                    
 
                                                         
 
                                                
     
                                                                          
     

 
                                  
 


                                    
 
                


 

               
 
      
                                        






                                                                                                            
 


                               

                                             
 

                   

















                                                                                                         

                                                                                  
 
                      



                                 
                                          
 
                                                                                             

                    
%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;
}