/*
   File: symbol_table.c
   Stores symbols with their associated segment and value

   CVS ID: "$Id: symbol_table.c,v 1.1 2006/10/11 13:41:07 marcs Exp $"
*/

/* include config.h if autoconfigured */
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif

/* global includes */
#include <stdio.h>
#include <string.h>

/* libabase includes */
#include <abase_porting.h>
#include <abase_error.h>
#include <abase_memalloc.h>

/* local includes */
#include "symbol_table.h"

/*
   Symbols are stored in an AVL tree with their associated information.
   We use an AVL tree since the output of the agfl-coder will contain
   lots of generated labels, whose lexicographic value is continuously
   rising.

   The search time for a symbol is bounded by 1.44 log (n+1) where n is
   the number of different symbols entered in the tree.
*/

/*
   The AVL tree implementation.
*/
struct symbol_rec
	{ struct symbol_rec *left;
	  struct symbol_rec *right;
	  char *key;
	  int segment;
	  int value;
	  signed char balfac;
	};

typedef symbol symbol_tree;
#define symbol_tree_nil ((symbol_tree) NULL)
static symbol_tree root;

/*
   Allocation of new leafs in the symbol tree
*/
static symbol_tree new_symbol_tree_leaf (char *sy)
	{ symbol new = (symbol_tree) abs_malloc (sizeof (struct symbol_rec),
						 "new_symbol_tree_leaf");
	  new -> left = symbol_tree_nil;
	  new -> right = symbol_tree_nil;
	  new -> key = abs_new_string (sy, "new_symbol_tree_leaf");
	  new -> segment = 0;
	  new -> value = 0;
	  new -> balfac = 0;
	  return (new);
	};

/*
   The insertion
*/
int enter_symbol (char *sy, symbol *entry)
	{ symbol_tree *fixation;
	  symbol_tree *insert_ptr;
	  symbol_tree last_unbal;
	  symbol_tree below_unbal;
	  symbol_tree twobelow_unbal;
	  symbol_tree ptr, new;

	  /* special case for initial tree */
	  if (root == symbol_tree_nil)
	    { root = new_symbol_tree_leaf (sy);
	      *entry = root;
	      return (1);
	    };
	 
	  /* search in tree */
	  insert_ptr = &root;
	  fixation = &root;
	  last_unbal = root;
	  while ((*insert_ptr) != symbol_tree_nil)
	    { int cond;
	      if ((*insert_ptr) -> balfac)
		{ fixation = insert_ptr;
		  last_unbal = *insert_ptr;
		};
	      cond = strcmp (sy, (*insert_ptr) -> key);
	      if (cond < 0) insert_ptr = &((*insert_ptr) -> left);
	      else if (cond > 0) insert_ptr = &((*insert_ptr) -> right);
	      else
		{ *entry = *insert_ptr;
		  return (0);
		};
	    };

	  /* found location to insert new leaf and remember it */
	  /* the pointer rotations may not leave *insert_ptr intact */
	  new = new_symbol_tree_leaf (sy);
	  *entry = new;
	  *insert_ptr = new;

	  /* adjust balance factors from last_unbal to the inserted node */
	  if (strcmp (sy, last_unbal -> key) < 0)
	    { below_unbal = last_unbal -> left;
	      last_unbal -> balfac--;
	    }
	  else
	    { below_unbal = last_unbal -> right;
	      last_unbal -> balfac++;
	    };

	  ptr = below_unbal;
	  while (ptr != new)
	     if (strcmp (sy, ptr -> key) < 0)
		{ ptr -> balfac--;
		  ptr = ptr -> left;
		}
	     else
		{ ptr -> balfac++;
		  ptr = ptr -> right;
		};

	  /* if tree not too much out of balance, done */
	  if ((-1 <= last_unbal -> balfac) && (last_unbal -> balfac <= 1))
	     return (1);

	  /* if last_unbal has the same sign as below_unbal, it is easy */
	  if (((last_unbal -> balfac > 0) && (below_unbal -> balfac > 0)) ||
	      ((last_unbal -> balfac < 0) && (below_unbal -> balfac < 0)))
	     { if (last_unbal -> balfac > 0)
		  { last_unbal -> right = below_unbal -> left;
		    below_unbal -> left = last_unbal;
		  }
	       else
		  { last_unbal -> left = below_unbal -> right;
		    below_unbal -> right = last_unbal;
		  }
	       last_unbal -> balfac = 0;
	       below_unbal -> balfac = 0;
	       *fixation = below_unbal;
	       return (1);
	     };

	  /* The difficult case */
	  if (last_unbal -> balfac > 0)
	     { twobelow_unbal = below_unbal -> left;
	       last_unbal -> right = twobelow_unbal -> left;
	       below_unbal -> left = twobelow_unbal -> right;
	       twobelow_unbal -> left = last_unbal;
	       twobelow_unbal -> right = below_unbal;
	       last_unbal -> balfac = (twobelow_unbal -> balfac == 1)?-1:0;
	       below_unbal -> balfac = (twobelow_unbal -> balfac == -1)?1:0;
	     }
	  else
	     { twobelow_unbal = below_unbal -> right;
	       last_unbal -> left = twobelow_unbal -> right;
	       below_unbal -> right = twobelow_unbal -> left;
	       twobelow_unbal -> right = last_unbal;
	       twobelow_unbal -> left = below_unbal;
	       last_unbal -> balfac = (twobelow_unbal -> balfac == -1)?1:0;
	       below_unbal -> balfac = (twobelow_unbal -> balfac == 1)?-1:0;
	     };
	  twobelow_unbal -> balfac = 0;
	  *fixation = twobelow_unbal;
	  return (1);
	};

/*
   Lookup
*/
int lookup_symbol (char *sy, symbol *entry)
	{ symbol_tree ptr = root; 
	  while (1)
	    { int cond;
	      if (ptr == symbol_tree_nil) return (0);
	      cond = strcmp (sy, ptr -> key);
	      if (cond < 0) ptr = ptr -> left;
	      else if (cond > 0) ptr = ptr -> right;
	      else
		{ *entry = ptr;
		  return (1);
		};
	    };
	};

/*
   Getting and updating of the symbol info
*/
void get_symbol_info (symbol sy, int *segment, int *value)
	{ *segment = sy -> segment;
	  *value = sy -> value;
	};

void update_symbol_info (symbol sy, int segment, int value)
	{ sy -> segment = segment;
	  sy -> value = value;
	}; 

/*
   Initialization
*/
void init_symbol_table ()
	{ root = symbol_tree_nil;
	};

/*
   Dump
*/
static void dump_symbol_tree (symbol_tree ptr, int depth)
	{ if (ptr != symbol_tree_nil)
	     { char bc = (ptr -> balfac < 0)?'-':(ptr -> balfac > 0)?'+':'0';
	       int ix;
	       dump_symbol_tree (ptr -> left, depth + 1);
	       for (ix = 0; ix < depth; ix++) abs_printf (" ");
	       abs_message (0, 1, "%c %s", bc, ptr -> key);
	       dump_symbol_tree (ptr -> right, depth + 1);
	     };
	};

void dump_symbol_table ()
	{ abs_message ("Dump of symbol table:");
	  dump_symbol_tree (root, 0);
	  abs_message ("");
	};
