/* Copyright (C) 1995 Bjoern Beutel. */

/* Description. =============================================================*/

/* This module manages the symbol tree for constants, variables and rules. */

/* Includes. ================================================================*/

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <setjmp.h>
#include <glib.h>
#include "basic.h"
#include "pools.h"
#include "files.h"
#include "values.h"
#include "rule_type.h"
#include "rule_code.h"
#include "avl_trees.h"
#include "rule_symbols.h"

/* Types. ===================================================================*/

typedef struct /* A definition of a variable and its scope. */
{ 
  list_node_t *next;
  int_t first_instr; /* First instruction where variable is accessible. */
  int_t last_instr; /* Last instruction where variable is accessible.
                     * -1 means "scope not closed yet". */
  int_t stack_index;
} comp_scope_t;

typedef struct /* A symbol table node. */
{ 
  avln_node_t node; /* A symbol node is an AVLN node. */
  int_t name_index; /* Index of NODE.NAME in CODE.STRING_POOL. */

  /* The associated rule. */
  int_t rule_number; /* Rule number in CODE.RULE_POOL or -1. */
  rule_t *rule_p; /* Rule pointer or NULL. */
  int_t rule_line; /* The source line of the rule definition. */
  string_t rule_file; /* The source file of the rule definition. */

  /* The associated constant. */
  int_t const_index; /* Index in CODE.VALUE_POOL or -1. */
  bool_t const_fixed; /* TRUE if constant is already fixed. */

  /* The associated variable scopes. */
  list_t scopes; 
} symbol_node_t;

/* Variables. ===============================================================*/

static avln_node_t *symbol_tree; /* Root node of the symbol tree.
				  * Actually points to symbol_node_t. */

/* Basic functions. =========================================================*/

static symbol_node_t *
find_symbol_node( string_t name )
/* Find and return a symbol node with NAME.
 * If that symbol node does not exist, create a new one. */
{
  symbol_node_t *node;
    
  /* Look for existing node. */
  node = (symbol_node_t *) find_avln_node( name, symbol_tree );
  if (node == NULL)  
  { 
    /* Node not found - create a new one. */
    node = new_mem( sizeof( symbol_node_t ) );
    node->node.name = copy_string_to_pool( code.string_pool, name, 
					   &node->name_index );
    node->rule_number = -1;
    node->rule_p = NULL;
    node->const_index = -1;
    clear_list( &node->scopes );
    insert_avln_node( (avln_node_t *) node, &symbol_tree );
  }
  return node;
}

/*---------------------------------------------------------------------------*/

static void 
free_symbol_tree( avln_node_t **node_p )
/* Free all memory used by *NODE_P (including the node itself). */
{
  comp_scope_t *scope;
  symbol_node_t *node = (symbol_node_t *) *node_p;

  if (*node_p == NULL) 
    return;
  free_symbol_tree( &(*node_p)->left );
  free_symbol_tree( &(*node_p)->right );
  FOREACH_FREE( scope, node->scopes ) 
    ; /* empty */
  free_mem( node_p );
}

/*---------------------------------------------------------------------------*/

void 
free_symbols( void )
/* Free all memory used by the symbol table. */
{
  free_symbol_tree( &symbol_tree );
}

/* Functions that use the symbol table for rules. ===========================*/

static void 
create_rule_node( symbol_node_t *node )
/* Create an empty rule entry for NODE. */
{
  rule_t rule;

  rule.name = node->name_index;
  rule.first_instr = -1;
  rule.type = -1;
  rule.param_count = -1;
  node->rule_p = (rule_t *) copy_to_pool( code.rule_pool, &rule, 1, 
					  &node->rule_number );
}

/*---------------------------------------------------------------------------*/

static string_t
rule_type_name( rule_type_t type )
/* Return TYPE as a readable string. */
{
  switch (type)
  {
  case ALLO_RULE: 
    return "allo-rule";
  case COMBI_RULE: 
    return "combi-rule";
  case END_RULE: 
    return "end-rule";
  case FILTER_RULE: 
    return "filter-rule";
  case PRUNING_RULE: 
    return "pruning-rule";
  case ROBUST_RULE: 
    return "robust-rule";
  case SUBRULE: 
    return "subrule";
  default: 
    complain( "Internal error." );
  }
}

/*---------------------------------------------------------------------------*/

string_t
get_rule_file( string_t name )
/* Return file where rule NAME is defined. */
{
  symbol_node_t *node;

  node = find_symbol_node( name );
  return node->rule_file;
}

/*---------------------------------------------------------------------------*/

int_t
get_rule_line( string_t name )
/* Return line where rule NAME is defined. */
{
  symbol_node_t *node;

  node = find_symbol_node( name );
  return node->rule_line;
}

/*---------------------------------------------------------------------------*/

void 
enter_function( string_t name, int_t index )
/* Associate standard function NAME with INDEX. */
{
  symbol_node_t *node;

  node = find_symbol_node( name );
  if (node->rule_p != NULL || node->rule_number != -1) 
    complain( "Function \"%s\" already defined.", name );
  node->rule_number = index;
}

/*---------------------------------------------------------------------------*/

int_t 
enter_rule( string_t name, int_t first_instr, rule_type_t type, 
	    int_t param_count, int rule_line, string_t rule_file )
/* Enter into the symbol table the rule NAME of TYPE that starts at FIRST_INSTR
 * and takes PARAM_COUNT parameters. The source is at RULE_LINE in RULE_FILE. 
 * The rule number will be returned. */
{
  symbol_node_t *node;

  node = find_symbol_node( name );
  if (node->rule_p != NULL) 
  { 
    if (node->rule_p->first_instr != -1) 
    {
      complain( "Rule \"%s\" already defined in \"%s\", line %d.", 
		name, name_in_path( node->rule_file ), node->rule_line );
    }
    else if (type == SUBRULE) 
    { 
      if (node->rule_p->type == COMBI_RULE) 
      {
	complain( "Rule \"%s\" already used as a combi rule or end rule "
		  "in \"%s\", line %d.", name, 
		  name_in_path( node->rule_file ), node->rule_line );
      }
      if (node->rule_p->param_count != param_count) 
      { 
	complain( "\"%s\" has been called with %d parameters "
		  "in \"%s\", line %d.", name, node->rule_p->param_count,
		  name_in_path( node->rule_file ), node->rule_line );
      }
    } 
    else if (node->rule_p->type == SUBRULE) 
    {
      complain( "Rule \"%s\" already used as a subrule in \"%s\", line %d.", 
		name, name_in_path( node->rule_file ), node->rule_line );
    } 
  } 
  else 
  { 
    if (node->rule_number != -1) 
      complain( "\"%s\" is a standard function." );
    create_rule_node( node );
  }
  node->rule_line = rule_line;
  node->rule_file = rule_file;
  node->rule_p->first_instr = first_instr;
  node->rule_p->type = type;
  node->rule_p->param_count = param_count;
  return node->rule_number;
}

/*---------------------------------------------------------------------------*/

rule_t *
find_subrule( string_t name, int_t *rule_number, int line, string_t file )
/* Find subrule or standard function NAME and return its rule descriptor.
 * If the rule descriptor is NULL, NAME denotes a standard function.
 * If RULE_NUMBER != NULL, save the rule index in *RULE_NUMBER. */
{
  symbol_node_t *node;

  node = find_symbol_node( name );
  if (node->rule_p == NULL)
  {
    if (node->rule_number == -1)
    {
      create_rule_node( node );
      node->rule_p->type = SUBRULE;
      node->rule_line = line;
      node->rule_file = file;
    }
  } 
  else if (node->rule_p->type != SUBRULE)
  {
    complain( "\"%s\" %s as a %s in \"%s\", line %d.", name, 
	      (node->rule_p->first_instr == -1 ? "used" : "defined"),
	      rule_type_name( node->rule_p->type ),
	      name_in_path( node->rule_file ), node->rule_line );
  }
  if (rule_number != NULL) 
    *rule_number = node->rule_number;
  return node->rule_p;
}

/*---------------------------------------------------------------------------*/

rule_t *
find_combi_rule( string_t name, int_t *rule_number, int line, string_t file )
/* Find combi-rule or end-rule NAME and return its rule descriptor.
 * If the rule descriptor is NULL, NAME describes a standard function.
 * If RULE_NUMBER != NULL, save the rule index in *RULE_NUMBER. */
{
  symbol_node_t *node;

  node = find_symbol_node( name );
  if (node->rule_p == NULL)
  {
    if (node->rule_number != -1) 
      complain( "\"%s\" is a function.", name );
    create_rule_node( node );
    node->rule_p->type = COMBI_RULE;
    node->rule_line = line;
    node->rule_file = file;
  }
  else if (node->rule_p->type != COMBI_RULE && node->rule_p->type != END_RULE)
  {
    complain( "\"%s\" %s as a %s in \"%s\", line %d.", name, 
	      (node->rule_p->first_instr == -1 ? "used" : "defined"),
	      rule_type_name( node->rule_p->type ),
	      name_in_path( node->rule_file ), node->rule_line );
  }
  if (rule_number != NULL) 
    *rule_number = node->rule_number;
  return node->rule_p;
}

/*---------------------------------------------------------------------------*/

static void 
check_rules_defined_local( avln_node_t *node )
/* If NODE is associated with a rule, check if it is defined. */
{
  symbol_node_t *sym_node = (symbol_node_t *) node;

  if (node == NULL) 
    return;
  check_rules_defined_local( node->left );
  if (sym_node->rule_p != NULL && sym_node->rule_p->first_instr == -1) 
  {
    if (in_emacs_malaga_mode) 
      printf( "SHOW \"%s\":%d:0\n", sym_node->rule_file, sym_node->rule_line );
    complain( "Undefined rule \"%s\". (\"%s\", line %d)", node->name, 
	      name_in_path( sym_node->rule_file ), sym_node->rule_line );
  }
  check_rules_defined_local( node->right );
}

/*---------------------------------------------------------------------------*/

void 
check_rules_defined( void )
/* Check if all rules in the symbol tree are defined. */
{
  check_rules_defined_local( symbol_tree );
}

/* Functions that use the symbol table for variables. =======================*/

int_t 
find_variable( string_t name )
/* Find variable NAME in the symbol table and return its stack index. */
{
  comp_scope_t *scope;

  scope = (comp_scope_t *) find_symbol_node( name )->scopes.last;
  if (scope == NULL || scope->last_instr != -1) 
    complain( "Variable \"$%s\" is not defined.", name );
  return scope->stack_index;
}

/*---------------------------------------------------------------------------*/

string_t 
define_variable( string_t name, int_t stack_index )
/* Define the value on index STACK_INDEX to be a new variable NAME.
 * Copy its name to the string pool and return the index of this copy. */
{
  symbol_node_t *node;
  comp_scope_t *scope;

  node = find_symbol_node( name );
  scope = (comp_scope_t *) node->scopes.last;
  if (scope != NULL && scope->last_instr == -1) 
    complain( "Variable \"$%s\" is defined twice.", name );

  /* Allocate a new scope and initialise it. */
  scope = new_node( &node->scopes, sizeof( comp_scope_t ), LIST_END );
  scope->first_instr = code.instr_count;
  scope->last_instr = -1; /* Scope is not yet closed. */
  scope->stack_index = stack_index;
  return node->node.name;
}

/*---------------------------------------------------------------------------*/

void 
undefine_variable( string_t name )
/* Close the scope of variable NAME. */
{
  comp_scope_t *scope;

  scope = (comp_scope_t *) find_symbol_node( name )->scopes.last;
  scope->last_instr = code.instr_count;
}

/*---------------------------------------------------------------------------*/

static void 
dump_variables_and_constants_local( avln_node_t *node )
/* Dump all variables and constants in NODE and its descendants. */
{
  comp_scope_t *scope; /* A scope in the symbol table. */
  var_t var; /* A new entry in CODE.VAR_POOL. */
  var_scope_t var_scope; /* A new entry in CODE.VAR_SCOPE_POOL. */
  constant_t constant;
  symbol_node_t *sym_node = (symbol_node_t *) node;

  if (node == NULL) 
    return;
  dump_variables_and_constants_local( node->left );

  /* Dump variables. */
  if (sym_node->scopes.first != NULL) 
  { 
    var.name = sym_node->name_index;
    var.first_scope = pool_item_count( code.var_scope_pool );
    var.scope_count = 0;

    /* Copy all scopes to the var-scope pool. */
    FOREACH( scope, sym_node->scopes ) 
    { 
      var_scope.first_instr = scope->first_instr;
      var_scope.last_instr = scope->last_instr;
      var_scope.stack_index = scope->stack_index;
      copy_to_pool( code.var_scope_pool, (void *) &var_scope, 1, NULL );
      var.scope_count++;
    }

    copy_to_pool( code.var_pool, (void *) &var, 1, NULL );
  }

  /* Dump constant. */
  if (sym_node->const_index != -1)
  {
    constant.name = sym_node->name_index;
    constant.value = sym_node->const_index;
    copy_to_pool( code.constant_pool, (void *) &constant, 1, NULL );
  }

  dump_variables_and_constants_local( node->right );
}

/*---------------------------------------------------------------------------*/

void 
dump_variables_and_constants( void )
/* Dump all variables and constants in the table. */
{
  /* Emit all variables and constants in alphabetical order. */
  dump_variables_and_constants_local( symbol_tree );
}

/* Functions that use the symbol table for constants. =======================*/

int_t 
find_constant( string_t name )
/* Find constant NAME in the symbol table and return its value pool index. */
{
  symbol_node_t *node;

  node = find_symbol_node( name );
  if (node->const_index == -1) 
    complain( "Constant \"@%s\" is not defined.", name );
  node->const_fixed = TRUE;
  return node->const_index;
}

/*---------------------------------------------------------------------------*/

void 
define_constant( string_t name, int_t const_index, bool_t fixed )
/* Define constant NAME with value at CONST_INDEX in the value pool.
 * If FIXED is FALSE, the given value is only a default value. */
{
  symbol_node_t *node;
  
  node = find_symbol_node( name );
  if (node->const_fixed) 
    complain( "Constant \"@%s\" is already defined.", name );
  if (! fixed && node->const_index != -1)
    complain( "Constant \"@%s\" already has a default value.", name );
  node->const_index = const_index;
  node->const_fixed = fixed;
}

/* End of file. =============================================================*/
