// ********************************************
// *                                          *
// *  SymbolTable.C                           *
// *                                          *
// *  Implementation for an SymbolTable class *
// *                                          *
// *  Author: Mariusz Zaczek                  *
// *    Date: 4/6/99                          *
// *   Class: Tu 11-1 Chad Peiper             *
// *                                          *
// ********************************************

#include "SymbolTable.h"

// operator==
//   - operator to compare if two Variables are equivalent
int operator==(const Variable & left, const Variable & right)
{
    if ( (left.value == right.value) && (left.nest_depth == right.nest_depth) )
      return 1;
    else
      return 0;
}

// operator<<
//   - required output operator...outputs Variable name and value
ostream & operator<<(ostream & Out, Variable & outputVariable)
{
    Out << "Value = "        << outputVariable.value 
        << "  Nest Depth = " << outputVariable.nest_depth << endl;
    return Out;
}


// SymbolTable
//   - default constructor
//   - initializes current_depth, the MyTable hash table
//     & the NameStk Stack
SymbolTable::SymbolTable()
{
  current_depth = 0;

  MyTable = new StringHash<Stack< Variable > * >;

  NameStk = new Stack<String>;
}


// ~SymbolTable
//   - destructor
//   - deletes dynamically allocated memory
SymbolTable::~SymbolTable()
{

  // Loop throught the NameStk stack and delete the dynamically
  //   allocated Stacks from the hash table.
  // These deletions occur at the 0 nest depth level while the
  //   ExitProcedure() deletions occur at levels 1 and above.
  MyTable -> Find(NameStk -> Top());

  while ( ( (MyTable -> Retrieve() ) -> Top() ).nest_depth == current_depth )
  {
    (MyTable -> Retrieve()) -> Pop();
    
     MyTable -> Find(NameStk -> Top());
     if ( (MyTable -> Retrieve()) -> Is_Empty() )
     {
       delete MyTable -> Retrieve();
       MyTable -> Remove(NameStk -> Top());
     }

     NameStk -> Pop();
    
     if ( NameStk -> Is_Empty() ) 
       break;
     else
       MyTable -> Find(NameStk -> Top());
  }

  delete NameStk;
  delete MyTable;

}

// Declare
//   - parameters : VarName - a String representing the name
//                   of the variable.
//   - creates a new Variable with the name, VarName, in the
//     current procedure.
void SymbolTable::Declare(String VarName)
{
  // Declare a new Variable and set it value to zero and nest depth to the
  //   current nest depth of the program.
  Variable newVar;
  newVar.value = 0;
  newVar.nest_depth = current_depth;

  // If the Variable of name, VarName, is already declared in the table
  //  and has the same nest depth as the one that exists
  //  then print out an error...
  // else if the variable is in the table but the nest depth is different
  //  then simply push a variable onto the Variable stack...
  // else make a new Stack of name VarName and insert this stack into the
  //  hash table.
  if ( MyTable -> Find(VarName) && 
     ((MyTable -> Retrieve()) -> Top() ).nest_depth == current_depth )
    cout << "Error: Re-declaration of variable " << VarName << endl;
  else if ( MyTable -> Find(VarName) )    // else if its in the table
  {
    ( MyTable -> Retrieve() ) -> Push(newVar);

    NameStk -> Push(VarName);
  }
  else
  {
    NameStk -> Push(VarName);

    Stack<Variable> *NewStack = new Stack<Variable>;

    NewStack -> Push(newVar);
  
    MyTable -> Insert(VarName,NewStack);
  }
  
  return;
}
   

// Write
//   - parameters : VarName - a String representing the name
//                   of the variable.
//                  VarValue - an in represening the value
//                   of the variable
//   - Assigns the value of VarValue to the Variable of name
//     VarName
void SymbolTable::Write(String VarName, int VarValue)
{
  // If the variable, VarName, is not found in the hash table the output
  //   error
  // else if it is in the table then compare it's nest depth to the current
  //   nest depth...if not equal then output error
  // else if nest depths are equal then remove top Variable from VarName 
  //   Stack and replace it with a new Variable containing the value, VarValue
  if ( !(MyTable -> Find(VarName)) )
    cout << "Variable " << VarName << " was not declared" << endl;
  else if ( ( (MyTable -> Retrieve()) -> Top() ).nest_depth != current_depth )
    cout << "Error: Write to out-of-scope variable " << VarName << endl; 
  else
  {
    Variable newVar = ( MyTable -> Retrieve() ) -> Pop();

    newVar.value = VarValue;
    ( MyTable -> Retrieve() ) -> Push(newVar);
  }
  return;
}
   

// Read
//   - parameters : VarName - a String representing the name
//                   of the variable
//   - read the value of the variable having name, VarName,
//     and returns.
int SymbolTable::Read(String VarName)
{
  int value = 0;
 
  // If the Stack, VarName, does not exist in the hash table then output
  //   error
  // else if it is on the table but it's nest depth is not equal to the
  //   current nest depth then also output error
  // else if nest depth's match then retrieve value of Variable and return it.
  if ( !(MyTable -> Find(VarName)) )
    cout << "Variable " << VarName << " was not declared" << endl
         << "Returning default value 0" << endl;
  else if ( ( (MyTable -> Retrieve()) -> Top() ).nest_depth != current_depth )
    cout << "Error: Read from out-of-scope variable " << VarName << endl
         << "Returning default value 0" << endl;
  else 
    value = ( (MyTable -> Retrieve()) -> Top() ).value;

  return value;
}

   

// CallProcedure
//   - parameters : none
//   - increases the nesting level of the program
void SymbolTable::CallProcedure()
{
  current_depth++;

  return;
}

   
// ExitProcedure
//   - parameters : none
//   - decreases the nesting level of the procedure and removes any  
//     variable declared or stored in the previous nesting level.
void SymbolTable::ExitProcedure()
{
  // If at the lowest level (i.e. main program) the output error
  // else you are at a higher nest level and wish to decrease one
  //   level. In order to accomplish this, you must remove all 
  //   variable declared at the current depth level and before exiting
  //   function decrease the current depth level.
  if (current_depth == 0)
  {
    cout << "Can not exit the main procedure this way" << endl;
    return;  
  }
  else
  {
    // Find the Variable at the top of the NameStk that is actually in
    //  the hash table...
    MyTable -> Find(NameStk -> Top());
    
    // While the current depth is the same as the Top variables then
    //   remove the variable from the hash table and remove the Variable
    //   name from the Name Stack (NameStk).
    while ( ( (MyTable -> Retrieve() ) -> Top() ).nest_depth == current_depth )
    {
      (MyTable -> Retrieve()) -> Pop();

      MyTable -> Find(NameStk -> Top());
      if ( (MyTable -> Retrieve()) -> Is_Empty() )
      {
	delete MyTable -> Retrieve();

        MyTable -> Remove(NameStk -> Top());
      }

      NameStk -> Pop();

      if ( NameStk -> Is_Empty() )
        break;
      else
        MyTable -> Find(NameStk -> Top());
    }
  }

  // Decrease nesting level (depth)
  current_depth--;

  return;
}
