/* ------------------------------------------------------------------------- */
/*   "verbs" :  Manages actions and grammar tables; parses the directives    */
/*              Verb and Extend.                                             */
/*                                                                           */
/*   Part of Inform 6    copyright (c) Graham Nelson 1993, 1994, 1995, 1996  */
/*                                                                           */
/* ------------------------------------------------------------------------- */

#include "header.h"

/* ------------------------------------------------------------------------- */
/*   Actions.                                                                */
/* ------------------------------------------------------------------------- */
/*   Array defined below:                                                    */
/*                                                                           */
/*    int32   action_byte_offset[n]       The (byte) offset in the Z-machine */
/*                                        code area of the ...Sub routine    */
/*                                        for action n.  (NB: This is left   */
/*                                        blank until the end of the         */
/*                                        compilation pass.)                 */
/*    int32   action_symbol[n]            The symbol table index of the n-th */
/*                                        action's name.                     */
/* ------------------------------------------------------------------------- */

int no_actions,                        /* Number of actions made so far      */
    no_fake_actions;                   /* Number of fake actions made so far */

static int debug_action_data_written[256];         /* TRUE when data written */

/* ------------------------------------------------------------------------- */
/*   Adjectives.                                                             */
/* ------------------------------------------------------------------------- */
/*   Arrays defined below:                                                   */
/*                                                                           */
/*    int32 adjectives[n]                 Byte address of dictionary entry   */
/*                                        for the nth adjective              */
/*    dict_word adjective_sort_code[n]    Dictionary sort code of nth adj    */
/* ------------------------------------------------------------------------- */

int no_adjectives;                     /* Number of adjectives made so far   */

/* ------------------------------------------------------------------------- */
/*   Verbs.  Note that Inform-verbs are not quite the same as English verbs: */
/*           for example the English verbs "take" and "drop" both normally   */
/*           correspond in a game's dictionary to the same Inform verb.  An  */
/*           Inform verb is essentially a list of grammar lines.             */
/* ------------------------------------------------------------------------- */
/*   Arrays defined below:                                                   */
/*                                                                           */
/*    verbt Inform_verbs[n]               The n-th grammar line sequence:    */
/*                                        see "header.h" for the definition  */
/*                                        of the typedef struct verbt        */
/*    int32 grammar_token_routine[n]      The byte offset from start of code */
/*                                        area of the n-th one               */
/* ------------------------------------------------------------------------- */

int no_Inform_verbs,                   /* Number of Inform-verbs made so far */
    no_grammar_token_routines;         /* Number of routines given in tokens */

/* ------------------------------------------------------------------------- */
/*   We keep a list of English verb-words known (e.g. "take" or "eat") and   */
/*   which Inform-verbs they correspond to.  (This list is needed for some   */
/*   of the grammar extension operations.)                                   */
/*   The format of this list is a sequence of variable-length records:       */
/*                                                                           */
/*     Byte offset to start of next record  (1 byte)                         */
/*     Inform verb number this word corresponds to  (1 byte)                 */
/*     The English verb-word (reduced to lower case), null-terminated        */
/* ------------------------------------------------------------------------- */

static char *English_verb_list,        /* First byte of first record         */
            *English_verb_list_top;    /* Next byte free for new record      */

static int English_verb_list_size;     /* Size of the list in bytes
                                          (redundant but convenient)         */

/* ------------------------------------------------------------------------- */
/*   Arrays used by this file                                                */
/* ------------------------------------------------------------------------- */

  verbt   *Inform_verbs;
  int32   *action_byte_offset,
          *action_symbol,
          *grammar_token_routine,
          *adjectives;
  static dict_word *adjective_sort_code;

/* ------------------------------------------------------------------------- */
/*   Tracing for compiler maintenance                                        */
/* ------------------------------------------------------------------------- */

extern void list_verb_table(void)
{   int i, j, k;
    for (i=0; i<no_Inform_verbs; i++)
    {   printf("Verb entry %2d  [%d]\n",i,Inform_verbs[i].lines);
        for (j=0; j<Inform_verbs[i].lines; j++)
        {   for (k=0; k<8; k++) printf("%03d ",Inform_verbs[i].l[j].e[k]);
            printf("\n");
        }
    }
}

/* ------------------------------------------------------------------------- */
/*   Actions.                                                                */
/* ------------------------------------------------------------------------- */

static void new_action(char *b, int c)
{
    /*  Called whenever a new action (or fake action) is created (either
        by using make_action above, or the Fake_Action directive, or by
        the linker).  At present just a hook for some tracing code.          */

    if (printprops_switch)
        printf("Action '%s' is numbered %d\n",b,c);
}

/* Note that fake actions are numbered from 256 upwards; real actions are    */
/* numbered from 255 down.                                                   */

extern void make_fake_action(void)
{   int i;
    char action_sub[MAX_IDENTIFIER_LENGTH+4];

    get_next_token();
    if (token_type != SYMBOL_TT)
    {   ebf_error("new fake action name", token_text);
        panic_mode_error_recovery(); return;
    }

    sprintf(action_sub, "%s__A", token_text);
    i = symbol_index(action_sub, -1);

    if (!(sflags[i] & UNKNOWN_SFLAG))
    {   ebf_error("new fake action name", token_text);
        panic_mode_error_recovery(); return;
    }

    assign_symbol(i, 256+no_fake_actions++, FAKE_ACTION_T);

    new_action(token_text, i);
    if (debugfile_switch)
    {   write_debug_byte(7); write_debug_byte(svals[i]);
        write_debug_string(token_text);
    }
    return;
}

extern assembly_operand action_of_name(char *name)
{
    /*  Returns the action number of the given name, creating it as a new
        action name if it isn't already known as such.                       */

    char action_sub[MAX_IDENTIFIER_LENGTH+4];
    int j;
    assembly_operand AO;

    sprintf(action_sub, "%s__A", name);
    j = symbol_index(action_sub, -1);

    if (stypes[j] == FAKE_ACTION_T)
    {   AO.value = svals[j];
        AO.marker = 0;
        AO.type = LONG_CONSTANT_OT;
        sflags[j] |= USED_SFLAG;
        return AO;
    }

    if (sflags[j] & UNKNOWN_SFLAG)
    {
        if (no_actions>=MAX_ACTIONS) memoryerror("MAX_ACTIONS",MAX_ACTIONS);
        new_action(name, no_actions);
        action_symbol[no_actions] = j;
        assign_symbol(j, no_actions++, CONSTANT_T);
        sflags[j] |= ACTION_SFLAG;
    }
    sflags[j] |= USED_SFLAG;

    AO.value = svals[j];
    AO.marker = ACTION_MV;
    AO.type = (module_switch)?LONG_CONSTANT_OT:SHORT_CONSTANT_OT;
    return AO;
}

extern void find_the_actions(void)
{   int i; int32 j;
    char action_sub[MAX_IDENTIFIER_LENGTH+4];

    if (module_switch)
        for (i=0; i<no_actions; i++) action_byte_offset[i] = 0;
    else
    for (i=0; i<no_actions; i++)
    {   strcpy(action_sub, (char *) symbs[action_symbol[i]]);
        strcpy(action_sub + strlen(action_sub) - 3, "Sub");
        j = symbol_index(action_sub, -1);
        if (sflags[j] & UNKNOWN_SFLAG)
            error_named("There is no action routine called", action_sub);
        else
        if (stypes[j] != ROUTINE_T)
            error_named("Not an action routine:", action_sub);
        else
        {   action_byte_offset[i] = svals[j];
            sflags[j] |= USED_SFLAG;
        }
    }
}

/* ------------------------------------------------------------------------- */
/*   Adjectives.                                                             */
/* ------------------------------------------------------------------------- */

static int make_adjective(char *English_word)
{
    /*  Returns adjective number of the English word supplied, creating
        a new adjective number if need be.

        Note that (partly for historical reasons) adjectives are numbered
        from 0xff downwards.  (And partly to make them stand out as tokens.) */

    int i; dict_word new_sort_code;

    new_sort_code = dictionary_prepare(English_word);
    for (i=0; i<no_adjectives; i++)
        if (compare_sorts(new_sort_code,adjective_sort_code[i]) == 0)
            return(0xff-i);
    adjectives[no_adjectives]
        = dictionary_add(English_word,8,0,0xff-no_adjectives);
    adjective_sort_code[no_adjectives] = new_sort_code;
    return(0xff-no_adjectives++);
}

/* ------------------------------------------------------------------------- */
/*   Parsing routines.                                                       */
/* ------------------------------------------------------------------------- */

static int make_parsing_routine(int32 routine_address)
{   int l;
    for (l=0; l<no_grammar_token_routines; l++)
        if (grammar_token_routine[l] == routine_address)
            return l;

    grammar_token_routine[l] = routine_address;
    return(no_grammar_token_routines++);
}

/* ------------------------------------------------------------------------- */
/*   The English-verb list.                                                  */
/* ------------------------------------------------------------------------- */

static int find_verb(char *English_verb)
{
    /*  Returns the Inform-verb number which the given English verb causes,
        or -1 if the given verb is not in the dictionary                     */

    char *p;
    p=English_verb_list;
    while (p < English_verb_list_top)
    {   if (strcmp(English_verb, p+2) == 0) return(p[1]);
        p=p+p[0];
    }
    return(-1);
}

static void register_verb(char *English_verb, int number)
{
    /*  Registers a new English verb as referring to the given Inform-verb
        number.  (See comments above for format of the list.)                */

    if (find_verb(English_verb) != -1)
    {   error_named("Two different verb definitions refer to", English_verb);
        return;
    }

    English_verb_list_size += strlen(English_verb)+3;
    if (English_verb_list_size >= MAX_VERBSPACE)
        memoryerror("MAX_VERBSPACE", MAX_VERBSPACE);

    English_verb_list_top[0] = 3+strlen(English_verb);
    English_verb_list_top[1] = number;
    strcpy(English_verb_list_top+2, English_verb);
    English_verb_list_top += English_verb_list_top[0];
}

static int get_verb(void)
{
    /*  Look at the last-read token: if it's the name of an English verb
        understood by Inform, in double-quotes, then return the Inform-verb
        that word refers to: otherwise give an error and return -1.          */

    int j;

    if ((token_type == DQ_TT) || (token_type == SQ_TT))
    {   j = find_verb(token_text);
        if (j==-1)
            error_named("There is no previous grammar for the verb",
                token_text);
        return j;
    }

    ebf_error("an English verb in quotes", token_text);

    return -1;
}

/* ------------------------------------------------------------------------- */
/*   Grammar lines for Verb/Extend directives.                               */
/* ------------------------------------------------------------------------- */

static int grammar_line(int verbnum, int line)
{
    /*  Parse a grammar line to be written into line number "line" on
        Inform-verb "verbnum".

        Syntax: * <token1> ... <token-n> -> <action>
                  (0 to 6 tokens here)

        is compiled to an 8-byte table:

            <no of noun-like parameters>  <t1> ... <t6>     <action number>
                                          (padded with 0s)

        Return TRUE if grammar continues after the line, FALSE if the
        directive comes to an end.                                           */

    int j, bytecode;
    int parameters, grammar_token;
    char action_subroutine[128];

    if (line >= MAX_LINES_PER_VERB)
    {   error("Too many lines of grammar for verb: increase #define \
MAX_LINES_PER_VERB"); return(0);
    }

    get_next_token();
    if ((token_type == SEP_TT) && (token_value == SEMICOLON_SEP)) return FALSE;
    if (!((token_type == SEP_TT) && (token_value == TIMES_SEP)))
    {   ebf_error("'*' divider", token_text);
        panic_mode_error_recovery();
        return FALSE;
    }

    /*  Clear the 8-byte table to zeros                                      */

    for (j=0; j<8; j++) Inform_verbs[verbnum].l[line].e[j]=0;

    grammar_token=1; parameters=0;

    do
    {   get_next_token();
        if ((token_type == SEP_TT) && (token_value == SEMICOLON_SEP))
        {   ebf_error("'->' clause", token_text);
            return FALSE;
        }
        if ((token_type == SEP_TT) && (token_value == ARROW_SEP)) break;

        if ((token_type == DQ_TT) || (token_type == SQ_TT))
        {    bytecode=make_adjective(token_text);
        }
        else if ((token_type==DIR_KEYWORD_TT)&&(token_value==NOUN_DK))
             {   get_next_token();
                 if ((token_type == SEP_TT) && (token_value == SETEQUALS_SEP))
                 {
                     /*  noun = <routine>                                    */

                     get_next_token();
                     if ((token_type != SYMBOL_TT)
                         || (stypes[token_value] != ROUTINE_T))
                     {   ebf_error("routine name after 'noun='", token_text);
                         panic_mode_error_recovery();
                         return FALSE;
                     }
                     bytecode = 16 + make_parsing_routine(svals[token_value]);
                     sflags[token_value] |= USED_SFLAG;
                     parameters++;
                 }
                 else
                 {   put_token_back(); parameters++; bytecode=0;
                 }
             }
        else if ((token_type==DIR_KEYWORD_TT)&&(token_value==HELD_DK))
             {   parameters++; bytecode=1; }
        else if ((token_type==DIR_KEYWORD_TT)&&(token_value==MULTI_DK))
             {   parameters++; bytecode=2; }
        else if ((token_type==DIR_KEYWORD_TT)&&(token_value==MULTIHELD_DK))
             {   parameters++; bytecode=3; }
        else if ((token_type==DIR_KEYWORD_TT)&&(token_value==MULTIEXCEPT_DK))
             {   parameters++; bytecode=4; }
        else if ((token_type==DIR_KEYWORD_TT)&&(token_value==MULTIINSIDE_DK))
             {   parameters++; bytecode=5; }
        else if ((token_type==DIR_KEYWORD_TT)&&(token_value==CREATURE_DK))
             {   parameters++; bytecode=6; }
        else if ((token_type==DIR_KEYWORD_TT)&&(token_value==SPECIAL_DK))
             {   parameters++; bytecode=7; }
        else if ((token_type==DIR_KEYWORD_TT)&&(token_value==NUMBER_DK))
             {   parameters++; bytecode=8; }
        else if ((token_type==DIR_KEYWORD_TT)&&(token_value==SCOPE_DK))
             {
                 /*  scope = <routine> */

                 get_next_token();
                 if (!((token_type==SEP_TT)&&(token_value==SETEQUALS_SEP)))
                 {   ebf_error("'=' after 'scope'", token_text);
                     panic_mode_error_recovery();
                     return FALSE;
                 }

                 get_next_token();
                 if ((token_type != SYMBOL_TT)
                     || (stypes[token_value] != ROUTINE_T))
                 {   ebf_error("routine name after 'scope='", token_text);
                     panic_mode_error_recovery();
                     return FALSE;
                 }

                 parameters++;
                 bytecode = 80 +
                     make_parsing_routine(svals[token_value]);
                 sflags[token_value] |= USED_SFLAG;
             }
        else if ((token_type == SEP_TT) && (token_value == SETEQUALS_SEP))
             {   error("'=' is only legal here as 'noun=Routine'");
                 panic_mode_error_recovery();
                 return FALSE;
             }
        else {   /*  <attribute>  or  <general-parsing-routine>  tokens      */

                 if ((token_type != SYMBOL_TT)
                     || ((stypes[token_value] != ATTRIBUTE_T)
                         && (stypes[token_value] != ROUTINE_T)))
                 {   error_named("No such grammar token as", token_text);
                     panic_mode_error_recovery(); return FALSE;
                 }
                 parameters++;
                 if (stypes[token_value]==ATTRIBUTE_T)
                      bytecode = 128 + svals[token_value];
                 else bytecode = 48 +
                          make_parsing_routine(svals[token_value]);
                 sflags[token_value] |= USED_SFLAG;
             }

        Inform_verbs[verbnum].l[line].e[grammar_token++]=bytecode;

    } while (TRUE);

    dont_enter_into_symbol_table = TRUE;
    get_next_token();
    dont_enter_into_symbol_table = FALSE;

    if (token_type != DQ_TT)
    {   ebf_error("name of new or existing action", token_text);
        panic_mode_error_recovery();
        return FALSE;
    }

    Inform_verbs[verbnum].l[line].e[0] = parameters;

    {   assembly_operand AO = action_of_name(token_text);
        j = AO.value;
        if (j>=256)
            error_named("This is a fake action, not a real one:", token_text);
    }
    Inform_verbs[verbnum].l[line].e[7] = j;

    if ((debugfile_switch)
        && (debug_action_data_written[j]==FALSE))
    {   action_subroutine[strlen(action_subroutine)-3]=0;
        write_debug_byte(8); write_debug_byte(j);
        write_debug_string(action_subroutine);
        debug_action_data_written[j]=TRUE;
    }

    return TRUE;
}

/* ------------------------------------------------------------------------- */
/*   The Verb directive:                                                     */
/*                                                                           */
/*       Verb [meta] "word-1" ... "word-n" | = "existing-English-verb"       */
/*                                         | <grammar-line-1> ... <g-line-n> */
/*                                                                           */
/* ------------------------------------------------------------------------- */

extern void make_verb(void)
{
    /*  Parse an entire Verb ... directive.                                  */

    int Inform_verb, meta_verb_flag=FALSE, verb_equals_form=FALSE;

    char *English_verbs_given[32]; int no_given = 0, i;

    directive_keywords.enabled = TRUE;

    get_next_token();

    if ((token_type == DIR_KEYWORD_TT) && (token_value == META_DK))
    {   meta_verb_flag = TRUE;
        get_next_token();
    }

    while ((token_type == DQ_TT) || (token_type == SQ_TT))
    {   English_verbs_given[no_given++] = token_text;
        get_next_token();
    }

    if (no_given == 0)
    {   ebf_error("English verb in quotes", token_text);
        panic_mode_error_recovery(); return;
    }

    if ((token_type == SEP_TT) && (token_value == SETEQUALS_SEP))
    {   verb_equals_form = TRUE;
        get_next_token();
        Inform_verb = get_verb();
        if (Inform_verb == -1) return;
        get_next_token();
        if (!((token_type == SEP_TT) && (token_value == SEMICOLON_SEP)))
            ebf_error("';' after English verb", token_text);
    }
    else
    {   Inform_verb = no_Inform_verbs;
        if (no_Inform_verbs == MAX_VERBS)
            memoryerror("MAX_VERBS",MAX_VERBS);
    }

    for (i=0; i<no_given; i++)
    {   dictionary_add(English_verbs_given[i],
            0x41 + ((meta_verb_flag)?0x02:0x00),
            0xff-Inform_verb, 0);
        register_verb(English_verbs_given[i], Inform_verb);
    }

    if (!verb_equals_form)
    {   int lines = 0;
        put_token_back();
        while (grammar_line(no_Inform_verbs, lines++)) ;
        Inform_verbs[no_Inform_verbs++].lines = --lines;
    }

    directive_keywords.enabled = FALSE;
}

/* ------------------------------------------------------------------------- */
/*   The Extend directive:                                                   */
/*                                                                           */
/*      Extend | only "verb-1" ... "verb-n"  |             <grammar-lines>   */
/*             | "verb"                      | "replace"                     */
/*                                           | "first"                       */
/*                                           | "last"                        */
/*                                                                           */
/* ------------------------------------------------------------------------- */

#define EXTEND_REPLACE 1
#define EXTEND_FIRST   2
#define EXTEND_LAST    3

extern void extend_verb(void)
{
    /*  Parse an entire Extend ... directive.                                */

    int Inform_verb, k, l, lines, extend_mode;

    directive_keywords.enabled = TRUE;
    directives.enabled = FALSE;

    get_next_token();
    if ((token_type == DIR_KEYWORD_TT) && (token_value == ONLY_DK))
    {   l = -1;
        if (no_Inform_verbs == MAX_VERBS)
            memoryerror("MAX_VERBS", MAX_VERBS);
        while (get_next_token(),
               ((token_type == DQ_TT) || (token_type == SQ_TT)))
        {   Inform_verb = get_verb();
            if (Inform_verb == -1) return;
            if ((l!=-1) && (Inform_verb!=l))
              warning_named("Verb disagrees with previous verbs:", token_text);
            l = Inform_verb;
            dictionary_set_verb_number(token_text, 0xff-no_Inform_verbs);
        }

        /*  Copy the old Inform-verb into a new one which the list of
            English-verbs given have had their dictionary entries modified
            to point to                                                      */

        Inform_verbs[no_Inform_verbs] = Inform_verbs[Inform_verb];
        Inform_verb = no_Inform_verbs++;
    }
    else
    {   Inform_verb = get_verb();
        if (Inform_verb == -1) return;
    }

    /*  Inform_verb now contains the number of the Inform-verb to extend...  */

    extend_mode = EXTEND_LAST;

    get_next_token();
    if ((token_type == SEP_TT) && (token_value == TIMES_SEP))
        put_token_back();
    else
    {   extend_mode = 0;
        if ((token_type == DIR_KEYWORD_TT) && (token_value == REPLACE_DK))
            extend_mode = EXTEND_REPLACE;
        if ((token_type == DIR_KEYWORD_TT) && (token_value == FIRST_DK))
            extend_mode = EXTEND_FIRST;
        if ((token_type == DIR_KEYWORD_TT) && (token_value == LAST_DK))
            extend_mode = EXTEND_LAST;

        if (extend_mode==0)
        {   ebf_error("'replace', 'last', 'first' or '*'", token_text);
            extend_mode = EXTEND_LAST;
        }
    }

    l = Inform_verbs[Inform_verb].lines;
    lines = 0;
    if (extend_mode == EXTEND_LAST) lines=l;
    do
    {   if (extend_mode == EXTEND_FIRST)
            for (k=l; k>0; k--)
                 Inform_verbs[Inform_verb].l[k+lines]
                     = Inform_verbs[Inform_verb].l[k-1+lines];
    } while (grammar_line(Inform_verb, lines++));

    if (extend_mode == EXTEND_FIRST)
    {   Inform_verbs[Inform_verb].lines = l+lines-1;
        for (k=0; k<l; k++)
            Inform_verbs[Inform_verb].l[k+lines-1]
                = Inform_verbs[Inform_verb].l[k+lines];
    }
    else Inform_verbs[Inform_verb].lines = --lines;

    directive_keywords.enabled = FALSE;
    directives.enabled = TRUE;
}

/* ========================================================================= */
/*   Data structure management routines                                      */
/* ------------------------------------------------------------------------- */

extern void init_verbs_vars(void)
{
    no_fake_actions = 0;
    no_actions = 0;
    English_verb_list_size = 0;

    Inform_verbs = NULL;
    action_byte_offset = NULL;
    grammar_token_routine = NULL;
    adjectives = NULL;
    adjective_sort_code = NULL;
    English_verb_list = NULL;

}

extern void verbs_begin_pass(void)
{   int i;

    no_Inform_verbs=0; no_adjectives=0;
    no_grammar_token_routines=0;
    no_actions=0;
    for (i=0; i<256; i++) debug_action_data_written[i]=FALSE;

    no_fake_actions=0;
}

extern void verbs_allocate_arrays(void)
{
    Inform_verbs          = my_calloc(sizeof(verbt),   MAX_VERBS, "verbs");
    action_byte_offset    = my_calloc(sizeof(int32),   MAX_ACTIONS, "actions");
    action_symbol         = my_calloc(sizeof(int32),   MAX_ACTIONS,
                                "action symbols");
    grammar_token_routine = my_calloc(sizeof(int32),   MAX_ACTIONS,
                                "grammar token routines");
    adjectives            = my_calloc(sizeof(int32),   MAX_ADJECTIVES,
                                "adjectives");
    adjective_sort_code   = my_calloc(sizeof(dict_word), MAX_ADJECTIVES,
                                "adjective sort codes");

    English_verb_list     = my_malloc(MAX_VERBSPACE, "register of verbs");
    English_verb_list_top = English_verb_list;
}

extern void verbs_free_arrays(void)
{
    my_free(&Inform_verbs, "verbs");
    my_free(&action_byte_offset, "actions");
    my_free(&action_symbol, "action symbols");
    my_free(&grammar_token_routine, "grammar token routines");
    my_free(&adjectives, "adjectives");
    my_free(&adjective_sort_code, "adjective sort codes");
    my_free(&English_verb_list, "register of verbs");
}

/* ========================================================================= */
