/* parser.cc */
#include "parser.h"
#include <ctype.h>
#include "id_lookup.h"
#include <fstream.h>
#include "id_lookup.h"

bool flush_on_death = false;

/* dictionary mapping strings to parseTg's */
static dictionary<string, parseTag> parse_dict;

//bool parse_string( string & );

bool check_tag( token_queue &Q, parseTag pt ) {
  token *t = Q.peek();
  if( !t || *t != pt ) return true;
  Q.pop();
  return false;
}

bool expect_tag( token_queue &Q, parseTag pt, const char *fname ) {
  token *t = Q.pop();
  if( !t || *t != pt ) {
    dc_trace( TRACE_ERROR ) {
      cerr << fname << " -- expected \"" << parse_lbl[pt] << "\" before "
	   << t->pos_string() << ". found " << *t << "\n";
    }
    return true;
  }
  return false;
}

bool expect_label( token_queue &Q, string &label, 
		   const char *fname = "parser" ) {
  token *t = Q.pop();
  if( !t || t->is_tag() ) {
    dc_trace( TRACE_ERROR ) {
      cerr << fname << " -- expected label before " << t->pos_string() 
	   << ". found " << *t << "\n";
    }
    return true;
  }

  label = ( ( string_token * )t )->get();
  if( !valid_label( label ) ) {
    dc_trace( TRACE_ERROR ) {
      cerr << fname << " -- expected valid label, not \"" << label 
	   << "\" before " << t->pos_string() << "\n";
    }
    return true;
  }
  return false;
}


bool expect_path( token_queue &Q, string &label, 
		  const char *fname = "parser" ) {
  token *t = Q.pop();
  if( !t || t->is_tag() ) {
    dc_trace( TRACE_ERROR ) {
      cerr << fname << " -- expected label before " << Q.pos_string() << "\n";
    }
    return true;
  }

  label = ( ( string_token * )t )->get();
  if( !valid_path( label ) ) {
    dc_trace( TRACE_ERROR ) {
      cerr << fname << " -- expected valid label, not \"" 
	   << label << "\" before " << Q.pos_string() << "\n";
    }
    return true;
  }
  return false;
}

bool check_label( token_queue &Q, string &label ) {		
  token *t = Q.peek();
  if( !t || t->is_tag() ) return true;

  label = ( ( string_token * )t )->get();
  if( !valid_label( label ) ) {
    return true;
  }
  Q.pop();
  return false;
}


bool check_path( token_queue &Q, string &label ) {
  token *t = Q.peek();
  if( !t || t->is_tag() )
    return true;

  label = ( ( string_token * )t )->get();
  if( !valid_path( label ) ) {
    return true;
  }
  Q.pop();
  return false;
}

void init_parse( void ) {
  static inited = false;

  if( inited ) return;

  inited = true;
  for( int n = 0 ; n < ntg ; n++ ) {
    if( parse_lbl[ n ] != nil ) {
      parse_dict.insert( string( parse_lbl[n] ), ( parseTag )n );
    }
  }
  for( int n = 0 ; n < nsyms ; n++ ) {
    parse_dict.insert( string( syn_list[n].syn_string ), syn_list[n].pT );
    dc_trace( TRACE_ALL ) {
      cerr << "init_parse -- " << syn_list[n].syn_string << " is a synonym for "
	   << parse_lbl[ syn_list[n].pT ] << "\n";
    }
  }
}

stream_state::stream_state( istream &S ) {
  stream = &S;
  char_num = char_in_line = line_num = 1;
  string filename = "<no file>";
  owned = false;
  previous_tag = tag_error;

  valid = !( !( *stream ) );
}

stream_state::stream_state( string fn ) {
  stream = new ifstream( fn );
  char_num = char_in_line = line_num = 1;
  filename = fn;;
  owned = true;
  previous_tag = tag_error;

  valid = !( !( *stream ) );
}

stream_state::~stream_state( void ) {
  if( owned && stream ) delete( stream );
}

bool stream_state::remove_space( void ) {
  char c = stream->peek();
  if( isspace( c ) ) {
    stream->get();
    do {
      if( stream->eof() ) {
	stream->putback( c );
	return true;
      }
      if( c == '\n' ) {
	line_num++; 
	char_in_line = 1;
      } else {
	char_in_line++;
      }
      char_num++;
      c = stream->get();
    } while( isspace( c ) );
    stream->putback( c );
    return true;
  }
  return false;
}

stream_status stream_state::pop( string &s ) {
  s = "";

  if( stream->eof() ) return STR_EMPTY;
  char c = stream->peek();
  if( isspace( c ) ) {
    do {      
      c = stream->get();
      char_num++;
      if( c == '\n' ) {
	line_num++;
	char_in_line = 1;
      } else {
	char_in_line++;
      }
      if( stream->eof() ) {
	stream->putback( c );
	return STR_EMPTY;
      }
      c = stream->peek();
    } while( isspace( c ) );
  }

  bool in_quotes = false;
  bool was_slash = false;
  while( 1 ) {
    c = stream->get();  
    if( stream->eof() ) {
      stream->putback( c );
      if_trc( in_quotes, TRACE_ERROR ) {
	cerr << "<file \"" << filename << "\"> ended inside quotes\n";
      }
      break;
    } else if ( c == '\n' ) {
      stream->putback( c );
      if_trc( in_quotes, TRACE_ERROR ) {
	cerr << "line " << line_num << " of <file \"" << filename 
	     << "\"> ended inside quotes\n";
      }
      break;
    }
    if( c == '\"' ) {
      if( in_quotes ) {
	in_quotes = was_slash;
      } else {
	in_quotes = true;
      }
    } else if( isspace( c ) && !in_quotes ) {
      stream->putback( c );
      break;
    }
    was_slash = ( c == '\\' );
    char_num++;
    char_in_line++;
    s += c;
  }
  return STR_GOOD;
}

string stream_state::pos_string( void ) {
  return ( string( "<file \"" ) + filename +
	   string( "\":line %d char %d>", line_num, char_num ) );
}

void stream_state::set_previous_tag( tag T ) {
  if( previous_tag ) {
    dc_label *l = lookup( previous_tag );
    if( l ) {
      switch( l->sub_type() ) {
      case Element_t :
	( ( ( dc_element * )l )->buffer_info() ).next_tag = T;
	break;
      case Component_t : case Modification_t : case GA_t :
	( ( ( dc_component * )l )->buffer_info() ).next_tag = T;
	break;
      default : ;
      }
    }
  }
  previous_tag = T;
}

token_queue::token_queue( string filename ) {
  c_state = nil;
  if( !openfile( filename ) ) {
    dc_trace( TRACE_FEW ) {
      cerr << "token_queue -- opening <file \"" << filename << "\">\n";
    }
  }
  token_num = 1;
}

token_queue::token_queue( istream &stream ) {
  state_stack.push( new stream_state( stream ) );
  token_num = 1;
  c_state = state_stack.pop();
} 

token_queue::~token_queue( string &s ) {
  if( flush_on_death ) {
    if_trc( !Q.empty() || !waitQ.empty(), TRACE_ERROR ) {
      cerr << "Flush on Death near " << pos_string() << "\n";
      flush_out();
    }
  } 
  token *t;
  forall( t, Q ) {
    delete( t );
  }
  Q.clear();
  while( !waitQ.empty() ) {
    delete( waitQ.pop() );
  }
  forall( t, used_Q ) {
    delete( t );
  }
  used_Q.clear();
}

void token_queue::clear( void ) {
  token *t;
  forall( t, used_Q ) {
    delete( t );
  }
  used_Q.clear();
}


void token_queue::flush( void ) {
  token *t;
  forall( t, used_Q ) {
    delete( t );
  }
  used_Q.clear();
  forall( t, Q ) {
    delete( t );
  }
  Q.clear();
  while( !waitQ.empty() ) {
    delete( waitQ.pop() );
  }
}

bool token_queue::openfile( string filename ) {
  stream_state *new_file = new stream_state( filename );
  if( !new_file->is_valid() ) {
    dc_trace( TRACE_ERROR ) {
      cerr << "openfile -- could not open <file \"" << filename 
	   << "\"> for read\n";
    }
    delete( new_file );
    return true;
  }
  if( c_state )
    state_stack.push( c_state );
  c_state = new_file;
  c_state->remove_space();
  return false;
}

bool token_queue::closefile( void ) {
  dc_trace( TRACE_FEW ) {
    cerr << "dc_trace_queue -- closing <file \"" << filename() << "\">\n";
  }
  delete( c_state );
  if( state_stack.empty() ) {
    c_state = nil;
    return true;
  }
  c_state = state_stack.pop();
  return false;
}

bool token_queue::decompose_string( void ) {
  /* NEG_SUM is true iff last token parsed was Neg_Sum_Tag.  next tag has to be 
     checked.  if it is a number then parse as negative number. otherwise treat 
     Neg_Sum_Tag as operator and add taf_token( Neg_Sum_Tag ).
     
     case of number with negative exponent( ex. 1e-1 ) is taken care of by 
     preparser */
  bool NEG_SUM = false; 

  string s;
  stream_status status = c_state->pop( s );

  if( status == STR_EMPTY ) return true;
  
  int pos = 0;
  int length = s.length();
  /* character position of start of this string */
  int C_Pos = c_state->get_char_pos() - length;
  /* line number of this string */
  int L_Pos = c_state->get_line_num();

  while( pos < length ) {
    /* if quote at start */
    if( s[pos] == '\"' ) {
      /* find position of ending quotation marks ignoring intervening '\"'s */
      int end_quote = pos;
      do {
	end_quote = s.pos( "\"", end_quote + 1 );
      } while( end_quote > 0 && s[end_quote - 1] == '\\' );

      if( end_quote == -1 ) {
	end_quote = length - 1;
      }
      string tkn_str = s( pos, end_quote );
      //parse_string( tkn_str );
      waitQ.append( new string_token( tkn_str, true, C_Pos + pos, L_Pos, 
				      filename() ) );
      pos = end_quote + 1;
    } else { /* else check for tag */
      int limit;
      if( ( limit = s.pos( "\"", pos ) ) == -1 ) limit = length;
      
      int tag_s = limit, tag_e = pos - 1;
      parseTag T = Invalid_Tag;
      for( int p = 0 ; p < npunc ; p++ ) {
	int punc_pos;
	if( ( punc_pos = s.pos( parse_lbl[ punc_s[p] ], pos ) ) != -1 &&
	    punc_pos < tag_s ) {
	  T = punc_s[p];
	  tag_s = punc_pos;
	  tag_e = punc_pos + strlen( parse_lbl[ T ] ) - 1;
	}
      }
      /* parse part of string from pos to tag_s */
      if( tag_s > pos ) {
	/* convert to lower case */
#ifndef PARSER_CASE_SENSITIVE
	for( int i = pos ; i < tag_s ; i++ ) { 
	  s[i] = tolower( s[i] );
	}
#endif
	string tkn_str = s( pos, tag_s - 1 );
	dic_item tag_item = parse_dict.lookup( tkn_str );
	if( tag_item == nil ) {
	  if( NEG_SUM ) {
	    if( isdigit( s[pos] ) || ( pos < tag_s - 2 && s[pos] == '.' && 
				       isdigit( s[pos+1] ) ) ) {
	      tkn_str = string( parse_lbl[Neg_Sum_Tag] ) + tkn_str;
	    } else {
	      waitQ.append( new tag_token( Neg_Sum_Tag, C_Pos + pos - 
					   strlen( parse_lbl[Neg_Sum_Tag] ),
					   L_Pos, filename() ) );
	    }
	    NEG_SUM = false;
	  }

	  //parse_string( tkn_str );
	  waitQ.append( new string_token( tkn_str, false, C_Pos + pos, L_Pos,
					  filename() ) );
	} else {
	  if( NEG_SUM ) {
	    waitQ.append( new tag_token( Neg_Sum_Tag, C_Pos + pos - 
					 strlen( parse_lbl[Neg_Sum_Tag] ),
					 L_Pos, filename() ) );
	    NEG_SUM = false;
	  }
	  
	  parseTag t = parse_dict.inf( tag_item );
	  waitQ.append( new tag_token( t, C_Pos + pos, L_Pos, 
				       filename() ) );
	}
      }
      if( T != Invalid_Tag ) {
	if( NEG_SUM ) {
	  waitQ.append( new tag_token( Neg_Sum_Tag, C_Pos + pos - 
				       strlen( parse_lbl[Neg_Sum_Tag] ),
				       L_Pos, filename() ) );
	  NEG_SUM = false;
	}	

	if( T == Neg_Sum_Tag ) {
	  NEG_SUM = true;
	  pos = tag_e + 1;
	} else {
	  pos = tag_e + 1;
	  waitQ.append( new tag_token( T, C_Pos + pos, L_Pos, 
				       filename() ) );
	}
      } else {
	pos = limit;
      }
    }
  }

  if( NEG_SUM ) {
    waitQ.append( new tag_token( Neg_Sum_Tag, 
		  C_Pos + length - strlen( parse_lbl[Neg_Sum_Tag] ),
		  L_Pos, filename() ) );
  }
  return length == 0;
}

bool token_queue::get_token( void ) {
  bool exiting = false; /* used to insure that Over_Tag followed by EOF does not
			   exit two files */

  if( c_state == nil ) return true;

  /* parse one string */
  if( waitQ.empty() ) {
    while( decompose_string() ) { /* true returned on eof */
      exiting = true;
      if( closefile() ) {
	if( !waitQ.empty() ) {
	  break; /* tokenize this string since next get_token will
		    signal queue closed */
	}
	return true; /* end of all files and no string to tokenize so signal 
			queue closed */
      } else if( !waitQ.empty() ) break;
    }
  }
  
  /* tokenize s */
  token *t = waitQ.pop();

  string pos;
  int line_num;
  if( t->is_tag() ) {
    int num_open;
    string s;
    switch( ( ( tag_token * )t )->get() ) {
    case Over_Tag :
      if( !exiting ) {
	closefile();
      }
      return get_token();
    case Line_Comment_Tag :
      line_num = t->lnum();
      while( 1 ) {
	delete( t );
	if( waitQ.empty() && decompose_string() ) { /* true returned on eof */
	  break;
	}
	t = waitQ.top();
	if( t->lnum() > line_num ) break;
	waitQ.pop();
      }

      return get_token();
    case Open_Comment_Tag :
      num_open = 1;
      pos = t->pos_string();
      do {
	delete( t );
	if( waitQ.empty() && decompose_string() ) { /* true returned on eof */
	  dc_trace( TRACE_WARNING ) {
	    cerr << "token_queue::get_token -- warning : file \"" 
		 << c_state->get_filename() << "\" ended inside comment."
		 << " Top comment started at " << pos << "\n";
	  }
	  break;
	}
	t = waitQ.pop();
	if( t->is_tag() ) {
	  switch( ( ( tag_token * )t )->get() ) {
	  case Open_Comment_Tag :
	    num_open++;
	    break;
	  case Close_Comment_Tag :
	    num_open--;
	  default : ;
	  }
	}
      } while( num_open );
      delete( t );
      return get_token();
    case Include_Tag :
      if( waitQ.empty() ) {
	if( decompose_string() ) {
	  dc_trace( TRACE_ERROR ) {
	    cerr << "token_queue::get_token -- no filename following "
		 << parse_lbl[ Include_Tag ] << " near " << pos << "\n";
	  }
	  return true;
	}
      }
      delete( t );
      t = waitQ.pop();
      if( t->is_tag() ) {
	dc_trace( TRACE_ERROR ) {
	  cerr << "token_queue::get_token -- no filename following "
	       << parse_lbl[ Include_Tag ] << " near " << pos << "\n";
	}
	delete( t );
	return true;
      }
      s = ( ( string_token * )t )->get();
      dequote( s );
      delete( t );
      if( openfile( s ) ) {
	dc_trace( TRACE_ERROR ) {
	  cerr << "token_queue::get_token -- could not include <file \"" << s 
	       << "\"> from " << pos << "\n";
	}
	return true;
      } else {
	dc_trace( TRACE_FEW ) {
	  cerr << "token_queue::get_token -- including <file \"" << s 
	       << "\"> from " << pos << "\n";
	}
      }
      return get_token();
    default :
      Q.append( t );
      return false;
    }
  } else {
    Q.append( t );
    return false;
  }
}

token *token_queue::pop( void ) {
  if( Q.empty() ) {
    if( get_token() ) {
      dc_trace( TRACE_SOME ) {
	cerr << "token_queue::pop -- could not parse token from stream\n";
      }
      return nil;
    }
  }

  if( Q.empty() ) { cerr << "ABOUT TO POP AN EMPTY Q\n"; }
  token *t = Q.pop();
  used_Q.append( t );
  dc_trace( TRACE_ALL ) {
    cerr << "POPPED " << *t << "\n";
  }

  token_num++;
  return t;
}

token *token_queue::extract( void ) {
  if( Q.empty() ) {
    if( get_token() ) {
      dc_trace( TRACE_SOME ) {
	cerr << "token_queue::extract -- could not parse token from stream\n";
      }
      return nil;
    }
  }

  if( Q.empty() ) { cerr << "ABOUT TO EXTRACT FROM AN EMPTY Q\n"; }
  token *t = Q.pop();
  dc_trace( TRACE_ALL ) {
    cerr << "EXTRACTED " << *t << "\n";
  }

  token_num++;
  return t;
}

token *token_queue::peek( void ) {
  if( Q.empty() ) {
    if( get_token() ) {
      dc_trace( TRACE_SOME ) {
	cerr << "token_queue::peek -- could not parse token from stream\n";
      }
      return nil;
    }
  }

  dc_trace( TRACE_ALL ) {
    cerr << "PEEKED at " << *Q.head() << "\n";
  }
  return Q.head();
}

void token_queue::push( cstring s, bool q ) {
  token *prev = peek();

  token *t;
  if( !prev )
    t = ( token * )new string_token( s, q );
  else
    t = ( token * )new string_token( s, q, prev->cnum(), prev->lnum(), 
				     prev->fname() );
  Q.push( t );
}

void token_queue::push( const parseTag pT ) {
  token *prev = peek();  

  token *t;
  if( !prev )
    t = ( token * )new tag_token( pT );
  else
    t = ( token * )new tag_token( pT, prev->cnum(), prev->lnum(), 
				  prev->fname() );

  Q.push( t );
}

void token_queue::push( token *t ) {
  if( t == nil ) {
    dc_trace( TRACE_ERROR ) {
      cerr << "token_queue::push -- attempted to push nil token\n";
    }
    return;
  }
  Q.push( t );
}

void token_queue::push_used( cstring s, bool q ) {
  token *next = peek();

  token *t;
  if( !next )
    t = ( token * )new string_token( s, q );
  else
    t = ( token * )new string_token( s, q, next->cnum(), next->lnum(), 
				     next->fname() );
  used_Q.append( t );
}

void token_queue::push_used( const parseTag pT ) {
  token *next = peek();

  token *t;
  if( !next )
    t = ( token * )new tag_token( pT );
  else
    t = ( token * )new tag_token( pT, next->cnum(), next->lnum(), 
				  next->fname() );

  used_Q.append( t );
}

void token_queue::push_used( token *t ) {
  if( t == nil ) {
    dc_trace( TRACE_ERROR ) {
      cerr << "token_queue::push -- attempted to push nil token\n";
    }
    return;
  }
  used_Q.append( t );
}

void token_queue::rewind( void ) {
  used_Q.conc( Q );
  Q.conc( used_Q );
}

void token_queue::flush_out( void ) {
  cerr << "Token Queue Contents:\n" << "---------------------\n";
  token *t;
  while( ( t = pop() ) != nil ) {
    cerr << "\t" << *t << "\t\t near " << t->pos_string() << "\n";
  }
}

string token_queue::pos_string( void ) { 
  token *t = peek();
  if( t ) 
    return t->pos_string(); 
  else 
    return string( "<end of input>" );
}

// bool parse_string( string &s ) {
//   /* remove matching quotes */
//   int mi = s.length() - 1; /* max index */
//   if( s[0] == '\"' ) {
//     if( s[mi] == '\"' ) { /* remove quotes */
//       s = s( 1, mi - 1 );
//       return false;
//     } else { /* unmatched quotes.  warn */
//       dc_trace( TRACE_WARNING )
// 	cerr << "parse_string -- unmatched quotes in string \"" << s << "\"\n";
//       return true;
//     }
//   } else {
//     if( s[mi] == '\"' ) {
//       /* unmatched quotes.  warn */
//       dc_trace( TRACE_WARNING )
// 	cerr << "parse_string -- unmatched quotes in string \"" << s << "\"\n";
//       return true;
//     }
//   }
//   return false;
// }

string token::pos_string( void ) const {
  return ( string( "<file \"" ) + filename +
	   string( "\":line %d char %d>", line_num, char_num ) );
}

ostream &operator<<( ostream &stream, const token &t ) {
  if( &t == nil ) {
    stream << "<end_of_stream>";
    return stream;
  }

  if( t.is_tag() ) {
    stream << "TAG    : " << parse_lbl[( int )( ( ( tag_token *)&t )->get() )];
  } else {
    const string *s = &( ( string_token * )&t )->get();
    if( ( *s )[0] == '\"' && ( *s )[s->length() - 1] == '\"' ) {
      stream << "STRING : " << *s;
    } else {
      stream << "STRING : '" << *s << "\'";
    }
  }
  return stream;
}

dc_type get_dtype( const parseTag pT ) {
  switch( pT ) {
  case Boolean_Tag : 
    return Boolean_t;
    break;
  case Distrib_Tag :
    return Distrib_t;
    break;
  case Int_Tag : 
    return Int_t;
    break;
  case Matrix_Tag : 
    return Rect_Matrix_t;
    break;
  case Ampersand_Tag : case Pointer_Tag :
    return Pointer_t;
    break;
  case Real_Tag : 
    return Real_t;
    break;
  case Set_Tag :
    return Set_t;
    break;
  case Shape_Tag :
    return Shape_t;
    break;
  case String_Tag :
    return String_t;
    break;
  case Symbol_Tag :
    return Symbol_t;
    break;
  case Triple_Tag :
    return Triple_t;
    break;
  case Vector_Tag :
    return Vector_t;
    break;
  default : return Undef_t;
  }
}

bool dequote( string &s ) {
  int length = s.length();
  if( length > 1 && s[0] == '\"' && s[length - 1] == '\"' ) {
    s = s( 1, length - 2 );
    return true;
  }
  return false;
}

bool quoted( cstring s ) {
  int length = s.length();
  return length > 1 && s[0] == '\"' && s[length - 1] == '\"';
}
