#include "io.hh"
File_Writer cout(stdout);
File_Writer cerr(stderr);
                                      
void string::worker_mutator_clone()
{
   if (ptr->ref_count > 1) {
      ptr->ref_count--;
      ASSERT(ptr->ref_count >= 1);
      String_Internal* old_ptr = ptr;
      ptr = (String_Internal*)malloc(sizeof(String_Internal) + old_ptr->maxlength);
      ptr->init(old_ptr->maxlength);
      ptr->length = my_strncpy(ptr->array,old_ptr->array,old_ptr->length+1);
      ASSERT(ptr->length <= ptr->maxlength);
      ASSERT(ptr->array[ptr->length] == '\0');
   }
}
                                      
string::string()
{
   ptr = (String_Internal*)malloc(sizeof(String_Internal) + String_Internal::MIN_LENGTH);
   ptr->init(String_Internal::MIN_LENGTH);
}
string::string(const string& s)
{
   worker_new_up(s);
   ptr = s.ptr;
}
string& string::operator = (const string& s)
{
   worker_new_up(s);             worker_current_down();
   ptr = s.ptr;
   return *this;
}
string::~string()
{
   worker_current_down();
}
                                      
string::string(char ch)
{
   ptr = (String_Internal*)malloc(sizeof(String_Internal) + String_Internal::MIN_LENGTH);
   ptr->init(String_Internal::MIN_LENGTH);
   string_buffer result;
   result << ch;
   *this = result;
}
string::string(int i)
{
   ptr = (String_Internal*)malloc(sizeof(String_Internal) + String_Internal::MIN_LENGTH);
   ptr->init(String_Internal::MIN_LENGTH);
   string_buffer result;
   result << i;
   *this = result;
}
string::string(double d)
{
   ptr = (String_Internal*)malloc(sizeof(String_Internal) + String_Internal::MIN_LENGTH);
   ptr->init(String_Internal::MIN_LENGTH);
   string_buffer result;
   result << d;
   *this = result;
}
string::string(const char* s)
{
   int len = strlen(s);
   int newlen = len + String_Internal::MIN_LENGTH;   ptr = (String_Internal*)malloc(sizeof(String_Internal) + newlen);
   ptr->init(newlen);
   ptr->length = my_strncpy(ptr->array,s,len+1);
   ASSERT(ptr->array[ptr->length] == '\0');
   ASSERT(ptr->length <= ptr->maxlength);
}
string& string::operator = (const char* s)
{
   worker_current_down();
   int len = strlen(s);
   int newlen = len + String_Internal::MIN_LENGTH;   ptr = (String_Internal*)malloc(sizeof(String_Internal) + newlen);
   ptr->init(newlen);
   ptr->length = my_strncpy(ptr->array,s,len+1);
   ASSERT(ptr->length <= ptr->maxlength);
   ASSERT(ptr->array[ptr->length] == '\0');
   return *this;
}
string& string::operator += (const string& s)
{
   int len1 = ptr->length;
   int len2 = s.ptr->length;
   if (len1 + len2 <= ptr->maxlength) {
      worker_mutator_clone();
      ptr->length += my_strncpy(ptr->array + ptr->length, s.ptr->array,len2+1);
      ASSERT(ptr->length <= ptr->maxlength);
      ASSERT(ptr->array[ptr->length] == '\0');
   } else {
      string_buffer sb;
      sb << ptr->array;
      sb << s.ptr->array;
      *this = sb;
   }
   return *this;
}
string& string::operator += (const char* s)
{
   int len1 = ptr->length;
   int len2 = strlen(s);
   if (len1 + len2 <= ptr->maxlength) {
      worker_mutator_clone();
      ptr->length += my_strncpy(ptr->array + ptr->length, s, len2+1);
      ASSERT(ptr->length <= ptr->maxlength);
      ASSERT(ptr->array[ptr->length] == '\0');
   } else {
      string_buffer sb;
      sb << ptr->array;
      sb << s;
      *this = sb;
   }
   return *this;
}
char* string::char_star(int mem_size, char* s) const
{
   my_strncpy(s,ptr->array,min(mem_size,ptr->length+1));
   return s;
}
char string::get_char_at(int index) const
{
   ASSERT(0 <= index);
   ASSERT(index < ptr->length);
   return ptr->array[index];
}
int string::get_length() const
{
   return ptr->length;
}
int my_strncpy(char* to, const char* from, int mem_size)
{
   if (from == null) {       *to = '\0';
      return 0;
   }
   int i = 0;
   int max = mem_size-1;    while ((*(to++) = *(from++))) {
      if (++i >= max) {
         *to = '\0';
         return max;
      }
   }
   return i;
}
                                      
string substring(const string& s, int start, int stop)
{
   int len = s.get_length();
if (start < 0) {
      start += len;
   }
   if (stop <= 0) {
      stop += len;
   }
   ASSERT(start >= 0);
   ASSERT(start < len);
   ASSERT(stop >= 0);
   ASSERT(stop <= len);
string_buffer answer;
for (int i=start; i<stop; i++) {
      answer << s.get_char_at(i);
   }
   return answer;
}
string quoted(const string& s)
{
   int len = s.get_length();
   string_buffer result;
   result << '"';
   for (int i=0; i<len; i++)
   {
      char ch = s.get_char_at(i);
      if (ch == '"') {
         result << '\\' << '"';
      }
      else if (ch == '\\') {
         result << '\\' << '\\';
      }
      else if (ch == '\t') {
         result << '\\' << 't';
      }
      else if (ch == '\n') {
         result << '\\' << 'n';
      }
      else if (ch == '\r') {
         result << '\\' << 'r';
      }
      else {
         result << ch;
      }
   }
   result << '"';
   return result;
}
string reversed(const string& s)
{
   string_buffer result;
for (int i=s.get_length()-1; i>=0; i--) {
result << s.get_char_at(i);
}
   return result;
}
string capitalised(const string& s)
{
   bool last_was_space = true;
   string_buffer result;
   int len = s.get_length();
   for (int i=0; i<len; i++) {
char ch = s.get_char_at(i);
if (last_was_space) {
         ch = toupper(ch);
      }
if (ch == ' ') {
         last_was_space = true;
      }
      else {
         last_was_space = false;
      }
result << ch;
   }
return result;
}
int index_of(const string& s, const string& key)
{
   int slen = s.get_length();
   int klen = key.get_length();
   for (int i=0; i<slen; i++) {
      bool found = true;
      for (int j=0; (i+j < slen) && (j<klen); j++) {
         if (key.get_char_at(j) != s.get_char_at(i+j)) {
            found = false;
            break;
         }
      }
      if (found) {
         return i;
      }
   }
   return -1;
}
                                      
void string_buffer::set_char_at(int i, char ch)
{
   worker_mutator_clone();
   ASSERT(i < ptr->length);
   ASSERT(i >= 0);
   ptr->array[i] = ch;
}
string_buffer& string_buffer::operator << (char ch)
{
   assert(ch != 0);
this->worker_mutator_clone();
if (this->ptr->length < this->ptr->maxlength) {
      this->ptr->array[this->ptr->length++] = ch;
      this->ptr->array[this->ptr->length]   = '\0';
   }
   else {
String_Internal* old_ptr = this->ptr;
      int len = old_ptr->length;
      int newlen = 2 * len;
      this->ptr = (String_Internal*)malloc(sizeof(String_Internal) + newlen);
      this->ptr->init(newlen);
      my_strncpy(this->ptr->array,old_ptr->array,len+1);
      this->ptr->length = len;
ASSERT(this->ptr->array[this->ptr->length] == '\0');
this->ptr->array[this->ptr->length++] = ch;
      this->ptr->array[this->ptr->length]   = '\0';
free(old_ptr);
   }
   return *this;
}
string_buffer& string_buffer::operator << (int i)
{
   this->worker_mutator_clone();
   char tmp[100];
   sprintf(tmp,"%d",i);
   *this << tmp;
   return *this;
}
string_buffer& string_buffer::operator << (double d)
{
   this->worker_mutator_clone();
   char tmp[100];
   sprintf(tmp,"%g",d);
   *this << tmp;
   return *this;
}
string_buffer& string_buffer::operator << (const char* s)
{
   bool dynamically_allocated_s = false;
int len = strlen(s);
if (len == 0) {
      return *this;
   }
if (this->ptr->array <= s && s <= this->ptr->array + len) {
      char* accumulator = new char[len+1];
      strcpy(accumulator, s);
      s = accumulator;
      dynamically_allocated_s = true;
   }
   this->worker_mutator_clone();
   while (*s != '\0') {
      if (this->ptr->length < this->ptr->maxlength) {
         this->ptr->array[this->ptr->length++] = *(s++);
      }
      else {
         this->ptr->array[this->ptr->length] = '\0';
         String_Internal* old_ptr = this->ptr;
         int len = old_ptr->length;
         int newlen = 2 * len;
         this->ptr = (String_Internal*)malloc(sizeof(String_Internal) + newlen);
         this->ptr->ref_count = 1;
         this->ptr->length    = len;
         this->ptr->maxlength = newlen;
         my_strncpy(this->ptr->array,old_ptr->array,len+1);
         ASSERT(this->ptr->array[this->ptr->length] == '\0');
         free(old_ptr);
      }
   }
   this->ptr->array[this->ptr->length] = '\0';
if (dynamically_allocated_s) {
      delete s;
   }
   return *this;
}
                                      
File_Gulper::File_Gulper(string file_name) : Gulper(file_name)
{
   const char* file_name_const_char_star = file_name.const_char_star();
this->f = fopen(file_name_const_char_star,"rb");
ASSERT_INFO(this->f != null, "Error opening file \"" + file_name + "\" for reading");
   this->saw_line_end = false;
   gulp_char();
}
void File_Gulper::gulp_char()
{
   current_char = fgetc(f);
   if (saw_line_end) {
      saw_line_end = false;
      file_line++;
   }
   if (current_char == '\n') {
      saw_line_end = true;
   }
}
                                      
String_Gulper::String_Gulper(string s) : Gulper("<string>")
{
   this->internal     = s;
   this->index        = -1;
   this->saw_line_end = false;
   gulp_char();
}
void String_Gulper::gulp_char()
{
   index++;
   if (index >= internal.get_length()) {
      index = internal.get_length();
      current_char = EOF;
      return;
   }
current_char = internal.get_char_at(index);
if (saw_line_end) {
      saw_line_end = false;
      file_line++;
   }
   if (current_char == '\n') {
      saw_line_end = true;
   }
}
                                      
string Reader_Core::scan_identifier()
{
   string_buffer s;
   s << (char)(gulper->get_current_char());
   gulper->gulp_char();
for (;;) {
int ch = gulper->get_current_char();
if (ch == EOF   ||
          ch == ';'   ||
          ch == '"'   ||
          isspace(ch) ||
          ch == '('   ||
          ch == ')'   ||
          ch == '\'') {
         return s;
      }
      s << (char)ch;
gulper->gulp_char();
   }
   return s;
}
string Reader_Core::scan_quoted_string()
{
   ASSERT(gulper->get_current_char() == '"');
   gulper->gulp_char();
string_buffer s;
while (gulper->get_current_char() != '"') {
char duplicate = gulper->get_current_char();
      if (gulper->get_current_char() == '\\') {
         gulper->gulp_char();
         switch (gulper->get_current_char()) {
         case 'n':
            duplicate = '\n';
            break;
         case 't':
            duplicate = '\t';
            break;
         case '\\':
            duplicate = '\\';
            break;
         case '\"':
            duplicate = '\"';
            break;
         case EOF:
            break;
         default:
            error_and_halt(string() +
                           "Unrecognised escape sequence: \\" +
                           (char)gulper->get_current_char());
         }
      }
if (gulper->get_current_char() == EOF) {
         error_and_halt(string() +
                        "End of file before end of string: " +
                        quoted(s));
         break;
      }
s << duplicate;
gulper->gulp_char();
   }
   ASSERT(gulper->get_current_char() == '"');
   gulper->gulp_char();
return s;
}
void Reader_Core::skip_whitespace()
{
   bool found_whites = true;
do {
found_whites = false;
if (isspace(gulper->get_current_char())) {
         found_whites = true;
         while (isspace(gulper->get_current_char())) {
            gulper->gulp_char();
         }
      }
if (gulper->get_current_char() == ';') {
         found_whites = true;
         int count=0;
         for (;;) {
            if (gulper->get_current_char() == '\n' || gulper->get_current_char() == EOF) break;
            gulper->gulp_char();
            ASSERT(++count < 1000);
         }
      }
   } while (found_whites);
}
class Io_Worker
{
public:
   static bool is_hex_digit(char ch)
   {
      ch = tolower(ch);
      return ((ch >= '0' && ch <= '9') ||
              (ch >= 'a' && ch <= 'f'));
   }
   static int hex_digit_value(char ch)
   {
      ch = tolower(ch);
      if (ch >= '0' && ch <= '9') {
         return ch - '0';
      } else if (ch >= 'a' && ch <= 'f') {
         return ch - 'a' + 10;
      } else {
         ASSERT(false);
      }
   }
};
int Reader_Core::scan_int()
{
   ASSERT(gulper->get_current_char() != '-');
int answer = 0;
if (gulper->get_current_char() == '0') {
      gulper->gulp_char();
      if (tolower(gulper->get_current_char()) == 'x') {
         gulper->gulp_char();
         while (Io_Worker::is_hex_digit(gulper->get_current_char())) {
            answer = answer * 16 + Io_Worker::hex_digit_value(gulper->get_current_char());
            gulper->gulp_char();
         }
         return answer;
      }
   }
while (gulper->get_current_char() == '0') {
      gulper->gulp_char();
   }
while (isdigit(gulper->get_current_char())) {
      answer = answer * 10 + (gulper->get_current_char() - '0');
      gulper->gulp_char();
   }
   return answer;
}
double Reader_Core::scan_fraction()
{
   double answer = 0;
   for (int k=10; isdigit(gulper->get_current_char()); k*=10) {
      answer = answer + (gulper->get_current_char() - '0') / ((double)k);
      gulper->gulp_char();
   }
   return answer;
}
void Reader_Core::next_token()
{
   skip_whitespace();
   bool minus_flag = false;
while (gulper->get_current_char() == '-') {
      gulper->gulp_char();
      minus_flag = !minus_flag;
   }
if (isdigit(gulper->get_current_char())) {
      last_int = scan_int();
      if (minus_flag) {
         last_int = (-last_int);
      }
      if (gulper->get_current_char() == '.') {
         
         gulper->gulp_char();
         last_double = last_int + (minus_flag? -1:1) * scan_fraction();
         if (tolower(gulper->get_current_char()) == 'e') {
            
            gulper->gulp_char();
            ASSERT(gulper->get_current_char() == '+' || gulper->get_current_char() == '-');
            bool exponent_negative;
            if (gulper->get_current_char() == '-') {
               exponent_negative = true;
            }
            else {
               exponent_negative = false;
            }
            gulper->gulp_char();
            int exponent = scan_int();
            if (exponent_negative) {
               exponent = (-exponent);
            }
            last_double = last_double * pow(10, exponent);
         }
         current_type = Type_Double;
      }
      else {
         current_type = Type_Int;
      }
   }
   else if (gulper->get_current_char() == '"') {
      current_type = Type_String;
      last_string = scan_quoted_string();
   }
   else if ((gulper->get_current_char() == '(') ||
            (gulper->get_current_char() == ')') ||
            (gulper->get_current_char() == '\'')) {
      current_type = Type_Char;
      last_char = gulper->get_current_char();
      gulper->gulp_char();
   }
   else if (gulper->get_current_char() == EOF) {
      current_type = Type_Eof;
   }
   else {
      current_type = Type_Ident;
      last_ident = scan_identifier();
   }
}
void Reader_Core::error_and_halt(string msg)
{
   (void)msg;
   ERROR(string() + "Parse error reading file ("
         + get_file_name()
         + ":"
         + get_file_line()
         + ")"
         + "\n\n Error = "
         + msg + "\n\n");
}
string Reader_Core::debug_say_symbol()
{
   string_buffer s;
switch (current_type) {
   case Type_Char:
      s << "(char, '" << last_char << "' (" << (int)last_char << "))";
      break;
   case Type_Int:
      s << "(int, " << last_int << ")";
      break;
   case Type_Double:
      s << "(double, " << last_double << ")";
      break;
   case Type_Ident:
      s << "(identifier, " << last_ident << ")";
      break;
   case Type_String:
      s << "(string, " << quoted(last_string) << ")";
      break;
   case Type_Eof:
      s << "(eof, gulper->get_current_char() = " << gulper->get_current_char()
        << ", type = " << current_type
        << ")";
      break;
   default:
      s << "(UNKNOWN! gulper->get_current_char() = " << gulper->get_current_char()
        << ", type = " << current_type << ")";
   }
   return s;
}
void Reader_Core::wrong_type(string tname)
{
   cout << "Coding error in Reader_Core call: tried to get_" << tname
        << ", but gulper->get_current_char() token = " << debug_say_symbol() << "\n";
   exit(EXIT_FAILURE);
}
                                      
Reader& operator >> (Reader& r, const char& expected_char)
{
   if ((expected_char != '(') &&
       (expected_char != ')') &&
       (expected_char != '\'')) {
      r.error_and_halt(string() +
                       "One of '(',')','\\'' must be passed to\n" +
                       "operator >> (Reader& r, const char& expected_char)\n" +
                       "expected_char='" + expected_char + "'");
   }
if (!r.currently_char()) {
      r.error_and_halt(string() + "A char '" + expected_char +
                       "' was expected but looking at " + r.debug_say_symbol());
   }
   if (r.peek_char() != expected_char) {
      r.error_and_halt(string() +
              "Expecting char '" + expected_char +
              "' but looking at char '" + r.peek_char() + '\'');
   }
   r.next_token();
   return r;
}
Reader& operator >> (Reader& r, const char* expected_string)
{
   const char* ptr = expected_string;
   bool found_space = false;
   bool found_illegal = false;
   string_buffer found_illegal_char;
   while ((*ptr) != '\0') {
      if (isspace(*ptr)) {
         found_space = true;
         break;
      } else if (*ptr == '(') {
         found_illegal = true;
         found_illegal_char << "'('";
         break;
      } else if (*ptr == ')') {
         found_illegal = true;
         found_illegal_char << "')'";
         break;
      } else if (*ptr == '\'') {
         found_illegal = true;
         found_illegal_char << "'\\''";
         break;
      }
      ptr++;
   }
if (found_space) {
      r.error_and_halt(string() + "Whitespace found in string: " + quoted(expected_string));
   }
   if (found_illegal) {
      r.error_and_halt(string() + "Illegal character " + found_illegal_char + " found in string: " + quoted(expected_string));
   }
if (!r.currently_identifier()) {
      r.error_and_halt(string() +
                       "An identifier \"" + expected_string +
                       "\" was expected but looking at " + r.debug_say_symbol());
   }
   if (!r.peek_identifier().equals(expected_string)) {
      r.error_and_halt(string() +
              "Expecting identifier " + quoted(expected_string) +
              " but looking at identifier " + quoted(r.peek_identifier()));
   }
   r.next_token();
   return r;
}
Reader& operator >> (Reader& r, char& ch)
{
   if (!r.currently_char()) {
      r.error_and_halt("A char was expected but looking at " + r.debug_say_symbol());
   }
   ch = r.peek_char();
   r.next_token();
   return r;
}
Reader& operator >> (Reader& r, int& i)
{
   if (!r.currently_int()) {
      r.error_and_halt("An int was expected but looking at " + r.debug_say_symbol());
   }
   i = r.peek_int();
   r.next_token();
   return r;
}
Reader& operator >> (Reader& r, double& d)
{
   if (!r.currently_double()) {
      r.error_and_halt("A double was expected but looking at " + r.debug_say_symbol());
   }
   d = r.peek_double();
   r.next_token();
   return r;
}
Reader& operator >> (Reader& r, string& s)
{
   if (!r.currently_identifier()) {
      r.error_and_halt("An identifier was expected but looking at " + r.debug_say_symbol());
   }
   s = r.peek_identifier();
   r.next_token();
   return r;
}
string Reader::gulp_quoted_string()
{
   ASSERT(currently_string());
   string s = peek_string();
   next_token();
   return s;
}