text: /*
  # Copyright (C) 1999-2021 The ViewCVS Group. All Rights Reserved.
  #
  # By using this file, you agree to the terms and conditions set forth in
  # the LICENSE.html file which can be found at the top level of the ViewVC
  # distribution or at http://viewvc.org/license-1.html.
  #
  # For more information, visit http://viewvc.org/
  #
  # -----------------------------------------------------------------------
  #
  # This file has been rewritten in C++ from the rcsparse.py file by
  # Lucas Bruand <lucas.bruand@ecl2002.ec-lyon.fr>
  #
  # This file was originally based on portions of the blame.py script by
  # Curt Hagenlocher.
  #
  # -----------------------------------------------------------------------
  */
  
  /*
     This C++ library offers an API to a performance oriented RCSFILE parser.
     It does little syntax checking.
   
     Version: $Id$	
  */
  
  #include "tparse.h"
  
  #ifndef __USE_XOPEN
  #define __USE_XOPEN
  #endif
  #include <ctime>   /* for strptime */
  
  
  using namespace std;
  
  #define Whitespace(c) (c == ' ' || c == '\t' || c == '\014' || c == '\n' || \
                         c == '\r')
  #define Token_term(c) (c == ' ' || c == '\t' || c == '\014' || c == '\n' || \
                         c == '\r' || c == ';' || c == ':')
  #define isdigit(c) ((c-'0')<10)
  
  
  
  void rcstoken::init(const char *mydata, size_t mylen)
  {
    size = DEFAULT_TOKEN_SIZE;
    length = 0;
    delta = DEFAULT_TOKEN_DELTA;
    data = NULL;
    if (mydata && mylen)
      append(mydata, mylen);
  };
  
  void rcstoken::append(const char *b, size_t b_len)
  {
    if (b || b_len)
      {
        grow(length + b_len + 1);
        memcpy(&data[length], b, b_len);
        length += b_len;
        data[length] = 0;
      }
  };
  
  void rcstoken::grow(size_t new_size)
  {
    if ((! data) || (new_size > size))
      {
        while (new_size > size)
          size += delta;
  
        data = (char*) realloc(data, size);
      };
  };
  
  rcstoken *rcstoken::copy_begin_end(size_t begin, size_t end)
  {
    return new rcstoken(&data[begin], end - begin);
  };
  
  rcstoken *rcstoken::copy_begin_len(size_t begin, size_t len)
  {
    return new rcstoken(&data[begin], len);
  };
  
  
  /*--------- Tokenparser class -----------*/
  rcstoken *TokenParser::get(int allow_eof)
  {
    auto_ptr<rcstoken> token;
  
    if (backget)
    {
      token.reset(backget);
      backget = NULL;
  
      return token.release();
    }
  
    token.reset(new rcstoken());
    while (1)
    {
      if (idx == buflength)
      {
        input->read(buf, CHUNK_SIZE);
        if ( (buflength = input->gcount()) == 0 )
        {
          if (allow_eof)
            return token.release();
          else
            throw RCSParseError("Unexpected end of file.");
        };
  
        idx = 0;
      }
      if (!Whitespace(buf[idx]))
        break;
      idx++;
    }
  
    if (buf[idx] == ';' || buf[idx] == ':')
    {
      idx++;
      (*token) = buf[idx];
      return token.release();
    }
  
    if (buf[idx] != '@')
    {
      int end = idx + 1;
  
      while (1)
      {
        while ( (end < buflength) && !(Token_term(buf[end])) )
          end++;
        token->append(buf + idx, end - idx);
        if (end < buflength)
        {
          idx = end;
          return token.release();
        }
        input->read(buf, CHUNK_SIZE);
        buflength = input->gcount();
        idx = 0;
        end = 0;
      }
    }
    idx++;
  
    while (1)
    {
      int i;
  
      if (idx == buflength)
      {
        idx = 0;
        input->read(buf, CHUNK_SIZE);
        if ( (buflength = input->gcount()) == 0 )
          throw RCSIllegalCharacter("Unterminated string: @ missing!");
      }
      //i=strchr(buf+idx,'@');
      for (i = idx;i < buflength && (buf[i] != '@');i++)
        ;
      if (i == buflength)
      {
        if ((buflength - idx) > 0)
          token->append(buf + idx, buflength - idx);
        idx = buflength;
        continue;
      }
      if ( i == buflength - 1)
      {
        token->append(buf + idx, i - idx);
        idx = 0;
        buf[0] = '@';
        input->read(buf + 1, CHUNK_SIZE - 1);
        if ( (buflength = input->gcount()) == 0 )
          throw RCSIllegalCharacter("Unterminated string: @ missing!");
        buflength++;
        continue;
      }
      if (buf[i + 1] == '@')
      {
        token->append(buf + idx, i - idx + 1);
        idx = i + 2;
        continue;
      }
      if ((i - idx) > 0)
        token->append(buf + idx, i - idx);
      idx = i + 1;
      return token.release();
    }
  };
  
  void TokenParser::unget(rcstoken *token)
  {
    if (backget)
    {
      throw RCSParseError("Ungetting a token while already having "
                          "an ungetted token.");
    }
    backget = token;
  }
  
  /*--------- tparseParser class -----------*/
  void tparseParser::parse_rcs_admin()
  {
    while (1)
    {
      auto_ptr<rcstoken> token(tokenstream->get(FALSE));
  
      if (isdigit((*token)[0]))
      {
        tokenstream->unget(token.release());
        return;
      }
      if (*token == "head")
      {
        token.reset(tokenstream->get(FALSE));
        sink->set_head_revision(*token);
  
        tokenstream->match(';');
        continue;
      }
      if (*token == "branch")
      {
        token.reset(tokenstream->get(FALSE));
        if (*token != ';')
        {
          sink->set_principal_branch(*token);
  
          tokenstream->match(';');
        }
        continue;
      }
      if (*token == "symbols")
      {
        while (1)
        {
          auto_ptr<rcstoken> rev;
          token.reset(tokenstream->get(FALSE));
          if (*token == ';')
            break;
  
          tokenstream->match(':');
          rev.reset(tokenstream->get(FALSE));
          sink->define_tag(*token, *rev);
        }
        continue;
      }
      if (*token == "comment")
      {
        token.reset(tokenstream->get(FALSE));
        sink->set_comment((*token));
  
        tokenstream->match(';');
        continue;
      }
      if (*token == "locks" ||
          *token == "strict" ||
          *token == "expand" ||
          *token == "access")
      {
        while (1)
        {
          token.reset(tokenstream->get(FALSE));
          if (*token == ';')
            break;
        }
        continue;
      }
    }
  };
  
  void tparseParser::parse_rcs_tree()
  {
    while (1)
    {
      auto_ptr<rcstoken> revision, date, author, hstate, next;
      long timestamp;
      tokenlist branches;
      struct tm tm;
  
      revision.reset(tokenstream->get(FALSE));
      if (*revision == "desc")
      {
        tokenstream->unget(revision.release());
        return;
      }
  
      // Parse date
      tokenstream->match("date");
      date.reset(tokenstream->get(FALSE));
      tokenstream->match(";");
  
      memset ((void *) &tm, 0, sizeof(struct tm));
      if (strptime((*date).data, "%y.%m.%d.%H.%M.%S", &tm) == NULL)
        strptime((*date).data, "%Y.%m.%d.%H.%M.%S", &tm);
      timestamp = mktime(&tm);
  
  
      tokenstream->match("author");
      author.reset(tokenstream->get(FALSE));
      tokenstream->match(';');
  
      tokenstream->match("state");
      hstate.reset(new rcstoken());
      while (1)
      {
        auto_ptr<rcstoken> token;
        token.reset(tokenstream->get(FALSE));
        if (*token == ';')
          break;
  
        if ((*hstate).length)
          (*hstate) += ' ';
        (*hstate) += *token;
      }
  
      tokenstream->match("branches");
      while (1)
        {
          auto_ptr<rcstoken> token;
          token.reset(tokenstream->get(FALSE));
          if (*token == ';')
            break;
  
          branches.push_front((*token));
        }
  
      tokenstream->match("next");
      next.reset(tokenstream->get(FALSE));
      if (*next == ';')
        /* generate null token */
        next.reset(new rcstoken());
      else
        tokenstream->match(';');
  
      /*
       * 	there are some files with extra tags in them. for example:
       *	owner	640;
       *	group	15;
       *	permissions	644;
       *	hardlinks	@configure.in@;
       *	this is "newphrase" in RCSFILE(5). we just want to skip over these.
       */
      while (1)
        {
          auto_ptr<rcstoken> token;
          token.reset(tokenstream->get(FALSE));
  
          if ((*token == "desc") || isdigit((*token)[0]) )
            {
              tokenstream->unget(token.release());
              break;
            };
  
          while (*token != ";")
            token.reset(tokenstream->get(FALSE));
        }
  
      sink->define_revision(*revision, timestamp, *author,
                            *hstate, branches, *next);
    }
    return;
  }
  
  void tparseParser::parse_rcs_description()
  {
    auto_ptr<rcstoken> token;
    tokenstream->match("desc");
  
    token.reset(tokenstream->get(FALSE));
    sink->set_description(*token);
  }
  
  void tparseParser::parse_rcs_deltatext()
  {
    auto_ptr<rcstoken> revision, log, text;
  
    while (1)
    {
      revision.reset(tokenstream->get(TRUE));
      if ((*revision).null_token())
        break;
  
      tokenstream->match("log");
      log.reset(tokenstream->get(FALSE));
  
      tokenstream->match("text");
      text.reset(tokenstream->get(FALSE));
  
      sink->set_revision_info(*revision, *log, *text);
    }
    return;
  }
  