/****************************************************************************/
/*                                                                          */
/*  Module:         jamsym.c                                                */
/*                                                                          */
/*                  Copyright (C) Altera Corporation 1997                   */
/*                                                                          */
/*  Description:    Functions for maintaining symbol table, including       */
/*                  adding a synbol, searching for a symbol, and            */
/*                  modifying the value associated with a symbol            */
/*                                                                          */
/*  Revisions:      1.1 added support for dynamic memory allocation, made   */
/*                  urj_jam_symbol_table a pointer table instead of a table of  */
/*                  structures.  Actual symbols now live at the top of the  */
/*                  workspace, and grow dynamically downwards in memory.    */
/*                                                                          */
/****************************************************************************/

#include <stdint.h>
#include "jamexprt.h"
#include "jamdefs.h"
#include "jamsym.h"
#include "jamheap.h"
#include "jamutil.h"

/****************************************************************************/
/*                                                                          */
/*  Global variables                                                        */
/*                                                                          */
/****************************************************************************/

JAMS_SYMBOL_RECORD **urj_jam_symbol_table = NULL;

void *urj_jam_symbol_bottom = NULL;

int urj_jam_init_symbol_table (void);
void urj_jam_free_symbol_table (void);
int urj_jam_check_init_list (char *name, int32_t *value);
int urj_jam_hash (const char *name);
int urj_jam_add_symbol (JAME_SYMBOL_TYPE type, char *name, int32_t value,
                    int32_t position);
int urj_jam_get_symbol_record (char *name, JAMS_SYMBOL_RECORD **symbol_record);
int urj_jam_get_symbol_value (JAME_SYMBOL_TYPE type, char *name, int32_t *value);
int urj_jam_set_symbol_value (JAME_SYMBOL_TYPE type, char *name, int32_t value);

/****************************************************************************/
/*                                                                          */

JAM_RETURN_TYPE
urj_jam_init_symbol_table (void)
/*                                                                          */
/*  Description:    Initializes the symbol table.  The symbol table is      */
/*                  located at the beginning of the workspace buffer.       */
/*                                                                          */
/*  Returns:        JAMC_SUCCESS for success, or JAMC_OUT_OF_MEMORY if the  */
/*                  size of the workspace buffer is too small to hold the   */
/*                  desired number of symbol records.                       */
/*                                                                          */
/****************************************************************************/
{
    int index = 0;
    JAM_RETURN_TYPE status = JAMC_SUCCESS;

    if (urj_jam_workspace != NULL)
    {
        urj_jam_symbol_table = (JAMS_SYMBOL_RECORD **) urj_jam_workspace;

        urj_jam_symbol_bottom = (void *) (((int32_t) urj_jam_workspace) +
                                      ((int32_t) urj_jam_workspace_size));

        if (urj_jam_workspace_size < (JAMC_MAX_SYMBOL_COUNT * sizeof (void *)))
        {
            status = JAMC_OUT_OF_MEMORY;
        }
    }
    else
    {
        urj_jam_symbol_table =
            (JAMS_SYMBOL_RECORD **)
            malloc ((JAMC_MAX_SYMBOL_COUNT * sizeof (void *)));

        if (urj_jam_symbol_table == NULL)
        {
            status = JAMC_OUT_OF_MEMORY;
        }
    }

    if (status == JAMC_SUCCESS)
    {
        for (index = 0; index < JAMC_MAX_SYMBOL_COUNT; ++index)
        {
            urj_jam_symbol_table[index] = NULL;
        }
    }

    return status;
}

void
urj_jam_free_symbol_table (void)
{
    int hash = 0;
    JAMS_SYMBOL_RECORD *symbol_record = NULL;
    JAMS_SYMBOL_RECORD *next = NULL;

    if ((urj_jam_symbol_table != NULL) && (urj_jam_workspace == NULL))
    {
        for (hash = 0; hash < JAMC_MAX_SYMBOL_COUNT; ++hash)
        {
            symbol_record = urj_jam_symbol_table[hash];
            while (symbol_record != NULL)
            {
                next = symbol_record->next;
                free (symbol_record);
                symbol_record = next;
            }
        }

        free (urj_jam_symbol_table);
    }
}

/****************************************************************************/
/*                                                                          */

int
urj_jam_check_init_list (char *name, int32_t *value)
/*                                                                          */
/*  Description:    Compares variable name to names in initialization list  */
/*                  and, if name is found, returns the corresponding        */
/*                  initialization value for the variable.                  */
/*                                                                          */
/*  Returns:        true if variable was found, else false                  */
/*                                                                          */
/****************************************************************************/
{
    char r, l;
    int ch_index = 0;
    int init_entry = 0;
    char *init_string = NULL;
    int32_t val;
    int match = false;
    int negate = false;
    int status = false;

    if (urj_jam_init_list != NULL)
    {
        while ((!match) && (urj_jam_init_list[init_entry] != NULL))
        {
            init_string = urj_jam_init_list[init_entry];
            match = true;
            ch_index = 0;
            do
            {
                r = toupper (init_string[ch_index]);
                if (!jam_is_name_char (r))
                    r = '\0';
                l = name[ch_index];
                match = (r == l);
                ++ch_index;
            }
            while (match && (r != '\0') && (l != '\0'));

            if (match)
            {
                --ch_index;
                while (isspace (init_string[ch_index]))
                    ++ch_index;
                if (init_string[ch_index] == JAMC_EQUAL_CHAR)
                {
                    ++ch_index;
                    while (isspace (init_string[ch_index]))
                        ++ch_index;

                    if (init_string[ch_index] == JAMC_MINUS_CHAR)
                    {
                        ++ch_index;
                        negate = true;
                    }

                    if (isdigit (init_string[ch_index]))
                    {
                        val = atol (&init_string[ch_index]);

                        if (negate)
                            val = 0L - val;

                        if (value != NULL)
                            *value = val;

                        status = true;
                    }
                }
            }
            else
            {
                ++init_entry;
            }
        }
    }

    return status;
}

/****************************************************************************/
/*                                                                          */

int
urj_jam_hash (const char *name)
/*                                                                          */
/*  Description:    Calcluates 'hash value' for a symbolic name.  This is   */
/*                  a pseudo-random number which is used as an offset into  */
/*                  the symbol table, as the initial position for the       */
/*                  symbol record.                                          */
/*                                                                          */
/*  Returns:        An integer between 0 and JAMC_MAX_SYMBOL_COUNT-1        */
/*                                                                          */
/****************************************************************************/
{
    int ch_index = 0;
    int hash = 0;

    while ((ch_index < JAMC_MAX_NAME_LENGTH) && (name[ch_index] != '\0'))
    {
        hash <<= 1;
        hash += (name[ch_index] & 0x1f);
        ++ch_index;
    }
    if (hash < 0)
        hash = 0 - hash;

    return hash % JAMC_MAX_SYMBOL_COUNT;
}

/****************************************************************************/
/*                                                                          */

JAM_RETURN_TYPE urj_jam_add_symbol
    (JAME_SYMBOL_TYPE type, char *name, int32_t value, int32_t position)
/*                                                                          */
/*  Description:    Adds a new symbol to the symbol table.  If the symbol   */
/*                  name already exists in the symbol table, it is an error */
/*                  unless the symbol type and the position in the file     */
/*                  where the symbol was declared are identical.  This is   */
/*                  necessary to allow labels and variable declarations     */
/*                  inside loops and subroutines where they may be          */
/*                  encountered multiple times.                             */
/*                                                                          */
/*  Returns:        JAMC_SUCCESS for success, or JAMC_REDEFINED_SYMBOL      */
/*                  if symbol was already declared elsewhere, or            */
/*                  JAMC_OUT_OF_MEMORY if symbol table is full.             */
/*                                                                          */
/****************************************************************************/
{
    char r, l;
    int ch_index = 0;
    int hash = 0;
    int32_t init_list_value = 0L;
    int match = false;
    int identical_redeclaration = false;
    JAM_RETURN_TYPE status = JAMC_SUCCESS;
    JAMS_SYMBOL_RECORD *symbol_record = NULL;
    JAMS_SYMBOL_RECORD *prev_symbol_record = NULL;

    /*
     *      Check for legal characters in name, and legal name length
     */
    while (name[ch_index] != JAMC_NULL_CHAR)
    {
        if (!jam_is_name_char (name[ch_index++]))
            status = JAMC_ILLEGAL_SYMBOL;
    }

    if ((ch_index == 0) || (ch_index > JAMC_MAX_NAME_LENGTH))
    {
        status = JAMC_ILLEGAL_SYMBOL;
    }

    /*
     *      Get hash key for this name
     */
    hash = urj_jam_hash (name);

    /*
     *      Get pointer to first symbol record corresponding to this hash key
     */
    symbol_record = urj_jam_symbol_table[hash];

    /*
     *      Then check for duplicate entry in symbol table
     */
    while ((status == JAMC_SUCCESS) && (symbol_record != NULL) &&
           (!identical_redeclaration))
    {
        match = true;
        ch_index = 0;
        do
        {
            r = symbol_record->name[ch_index];
            l = name[ch_index];
            match = (r == l);
            ++ch_index;
        }
        while (match && (r != '\0') && (l != '\0'));

        if (match)
        {
            /*
             *      Check if symbol was already declared identically
             *      (same name, type, and source position)
             */
            if ((symbol_record->position == position) &&
                (urj_jam_phase == JAM_DATA_PHASE))
            {
                if ((type == JAM_INTEGER_ARRAY_WRITABLE) &&
                    (symbol_record->type == JAM_INTEGER_ARRAY_INITIALIZED))
                {
                    type = JAM_INTEGER_ARRAY_INITIALIZED;
                }

                if ((type == JAM_BOOLEAN_ARRAY_WRITABLE) &&
                    (symbol_record->type == JAM_BOOLEAN_ARRAY_INITIALIZED))
                {
                    type = JAM_BOOLEAN_ARRAY_INITIALIZED;
                }
            }

            if ((symbol_record->type == type) &&
                (symbol_record->position == position))
            {
                /*
                 *      For identical redeclaration, simply assign the value
                 */
                identical_redeclaration = true;

                if (urj_jam_version != 2)
                {
                    symbol_record->value = value;
                }
                else
                {
                    if ((type != JAM_PROCEDURE_BLOCK) &&
                        (type != JAM_DATA_BLOCK) &&
                        (urj_jam_current_block != NULL) &&
                        (urj_jam_current_block->type == JAM_PROCEDURE_BLOCK))
                    {
                        symbol_record->value = value;
                    }
                }
            }
            else
            {
                status = JAMC_REDEFINED_SYMBOL;
            }
        }

        prev_symbol_record = symbol_record;
        symbol_record = symbol_record->next;
    }

    /*
     *      If no duplicate entry found, add the symbol
     */
    if ((status == JAMC_SUCCESS) && (symbol_record == NULL) &&
        (!identical_redeclaration))
    {
        /*
         *      Check initialization list -- if matching name is found,
         *      override the initialization value with the new value
         */
        if (((type == JAM_INTEGER_SYMBOL) || (type == JAM_BOOLEAN_SYMBOL)) &&
            (urj_jam_init_list != NULL))
        {
            if ((urj_jam_version != 2) &&
                urj_jam_check_init_list (name, &init_list_value))
            {
                /* value was found -- override old value */
                value = init_list_value;
            }
        }

        /*
         *      Add the symbol
         */
        if (urj_jam_workspace != NULL)
        {
            urj_jam_symbol_bottom = (void *)
                (((int32_t) urj_jam_symbol_bottom) - sizeof (JAMS_SYMBOL_RECORD));

            symbol_record = (JAMS_SYMBOL_RECORD *) urj_jam_symbol_bottom;

            if ((int32_t) urj_jam_heap_top > (int32_t) urj_jam_symbol_bottom)
            {
                status = JAMC_OUT_OF_MEMORY;
            }
        }
        else
        {
            symbol_record = (JAMS_SYMBOL_RECORD *)
                malloc (sizeof (JAMS_SYMBOL_RECORD));

            if (symbol_record == NULL)
            {
                status = JAMC_OUT_OF_MEMORY;
            }
        }

        if (status == JAMC_SUCCESS)
        {
            symbol_record->type = type;
            symbol_record->value = value;
            symbol_record->position = position;
            symbol_record->parent = urj_jam_current_block;
            symbol_record->next = NULL;

            if (prev_symbol_record == NULL)
            {
                urj_jam_symbol_table[hash] = symbol_record;
            }
            else
            {
                prev_symbol_record->next = symbol_record;
            }

            ch_index = 0;
            while ((ch_index < JAMC_MAX_NAME_LENGTH) &&
                   (name[ch_index] != '\0'))
            {
                symbol_record->name[ch_index] = name[ch_index];
                ++ch_index;
            }
            symbol_record->name[ch_index] = '\0';
        }
    }

    return status;
}

/****************************************************************************/
/*                                                                          */

JAM_RETURN_TYPE urj_jam_get_symbol_record
    (char *name, JAMS_SYMBOL_RECORD **symbol_record)
/*                                                                          */
/*  Description:    Searches in symbol table for a symbol record with       */
/*                  matching name.                                          */
/*                                                                          */
/*  Return:         Pointer to symbol record, or NULL if symbol not found   */
/*                                                                          */
/****************************************************************************/
{
    char r, l;
    char save_ch = 0;
    int ch_index = 0;
    int hash = 0;
    int name_begin = 0;
    int name_end = 0;
    int match = false;
    JAMS_SYMBOL_RECORD *tmp_symbol_record = NULL;
    JAM_RETURN_TYPE status = JAMC_UNDEFINED_SYMBOL;

    /*
     *      Get hash key for this name
     */
    hash = urj_jam_hash (name);

    /*
     *      Get pointer to first symbol record corresponding to this hash key
     */
    tmp_symbol_record = urj_jam_symbol_table[hash];

    /*
     *      Search for name in symbol table
     */
    while ((!match) && (tmp_symbol_record != NULL))
    {
        match = true;
        ch_index = 0;
        do
        {
            r = tmp_symbol_record->name[ch_index];
            l = name[ch_index];
            match = (r == l);
            ++ch_index;
        }
        while (match && (r != '\0') && (l != '\0'));

        if (match)
        {
            status = JAMC_SUCCESS;
        }
        else
        {
            tmp_symbol_record = tmp_symbol_record->next;
        }
    }

    /*
     *      For Jam version 2, check that symbol is in scope
     */
    if ((status == JAMC_SUCCESS) && (urj_jam_version == 2))
    {
        if (urj_jam_checking_uses_list &&
            ((tmp_symbol_record->type == JAM_PROCEDURE_BLOCK) ||
             (tmp_symbol_record->type == JAM_DATA_BLOCK)))
        {
            /* ignore scope rules when validating USES list */
            status = JAMC_SUCCESS;
        }
        else if ((tmp_symbol_record->parent != NULL) &&
                 (tmp_symbol_record->parent != urj_jam_current_block) &&
                 (tmp_symbol_record != urj_jam_current_block))
        {
            JAMS_SYMBOL_RECORD *parent = tmp_symbol_record->parent;
            JAMS_HEAP_RECORD *heap_record = NULL;
            char *parent_name = NULL;
            char *uses_list = NULL;

            status = JAMC_SCOPE_ERROR;

            /*
             *      If the symbol in question is a procedure name, check that IT
             *      itself is in the uses list, instead of looking for its parent
             */
            if (tmp_symbol_record->type == JAM_PROCEDURE_BLOCK)
            {
                parent = tmp_symbol_record;
            }

            if (parent != NULL)
            {
                parent_name = parent->name;
            }

            if ((urj_jam_current_block != NULL) &&
                (urj_jam_current_block->type == JAM_PROCEDURE_BLOCK))
            {
                heap_record = (JAMS_HEAP_RECORD *) urj_jam_current_block->value;

                if (heap_record != NULL)
                {
                    uses_list = (char *) heap_record->data;
                }
            }

            if ((uses_list != NULL) && (parent_name != NULL))
            {
                name_begin = 0;
                ch_index = 0;
                while ((uses_list[ch_index] != JAMC_NULL_CHAR) &&
                       (status != JAMC_SUCCESS))
                {
                    name_end = 0;
                    while ((uses_list[ch_index] != JAMC_NULL_CHAR) &&
                           (!jam_is_name_char (uses_list[ch_index])))
                    {
                        ++ch_index;
                    }
                    if (jam_is_name_char (uses_list[ch_index]))
                    {
                        name_begin = ch_index;
                    }
                    while ((uses_list[ch_index] != JAMC_NULL_CHAR) &&
                           (jam_is_name_char (uses_list[ch_index])))
                    {
                        ++ch_index;
                    }
                    name_end = ch_index;

                    if (name_end > name_begin)
                    {
                        save_ch = uses_list[name_end];
                        uses_list[name_end] = JAMC_NULL_CHAR;
                        if (strcmp (&uses_list[name_begin],
                                        parent_name) == 0)
                        {
                            /* symbol is in scope */
                            status = JAMC_SUCCESS;
                        }
                        uses_list[name_end] = save_ch;
                    }
                }
            }
        }
    }

    if (status == JAMC_SUCCESS)
    {
        if (symbol_record == NULL)
        {
            status = JAMC_INTERNAL_ERROR;
        }
        else
        {
            *symbol_record = tmp_symbol_record;
        }
    }
    return status;
}

/****************************************************************************/
/*                                                                          */

JAM_RETURN_TYPE urj_jam_get_symbol_value
    (JAME_SYMBOL_TYPE type, char *name, int32_t *value)
/*                                                                          */
/*  Description:    Gets the value of a symbol based on the name and        */
/*                  symbol type.                                            */
/*                                                                          */
/*  Returns:        JAMC_SUCCESS for success, else JAMC_UNDEFINED_SYMBOL    */
/*                  if symbol is not found                                  */
/*                                                                          */
/****************************************************************************/
{
    JAM_RETURN_TYPE status = JAMC_UNDEFINED_SYMBOL;
    JAMS_SYMBOL_RECORD *symbol_record = NULL;

    status = urj_jam_get_symbol_record (name, &symbol_record);

    if ((status == JAMC_SUCCESS) && (symbol_record != NULL))
    {
        /*
         *      If type and name match, return the value
         */
        if (symbol_record->type == type)
        {
            if (value != NULL)
            {
                *value = symbol_record->value;
            }
            else
            {
                status = JAMC_INTERNAL_ERROR;
            }
        }
        else
        {
            status = JAMC_TYPE_MISMATCH;
        }
    }

    return status;
}

/****************************************************************************/
/*                                                                          */

JAM_RETURN_TYPE urj_jam_set_symbol_value
    (JAME_SYMBOL_TYPE type, char *name, int32_t value)
/*                                                                          */
/*  Description:    Assigns the value corresponding to a symbol, based on   */
/*                  the name and symbol type                                */
/*                                                                          */
/*  Returns:        JAMC_SUCCESS for success, else JAMC_UNDEFINED_SYMBOL    */
/*                  if symbol is not found                                  */
/*                                                                          */
/****************************************************************************/
{
    JAM_RETURN_TYPE status = JAMC_UNDEFINED_SYMBOL;
    JAMS_SYMBOL_RECORD *symbol_record = NULL;

    status = urj_jam_get_symbol_record (name, &symbol_record);

    if ((status == JAMC_SUCCESS) && (symbol_record != NULL))
    {
        /* check for matching type... */
        if (symbol_record->type == type)
        {
            symbol_record->value = value;
        }
        else
        {
            status = JAMC_TYPE_MISMATCH;
        }
    }

    return status;
}
