/*
# Copyright (C) 1999-2020 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$
 */

#ifndef __PARSE_H
#define __PARSE_H
#include <memory>     /* for auto_ptr */
#include <algorithm>  /* for iterator */
#include <exception>  /* for exception */
#include <istream>    /* for istream */
#include <list>       /* for list<> */
#include <string>     /* for string */


#define CHUNK_SIZE 30000
#define DEFAULT_TOKEN_SIZE 512
#define DEFAULT_TOKEN_DELTA 10240

#ifndef FALSE
#define FALSE (0 != 0)
#endif

#ifndef TRUE
#define TRUE (0 == 0)
#endif

using namespace std;

/* This class represents a exception that occured during the parsing
   of a file */

class RCSParseError : public exception
{
  public:
    string value;
    RCSParseError() {};
    RCSParseError(const char *myvalue)
    {
      value = myvalue;
    };
    virtual ~RCSParseError() throw() {};
};

class RCSIllegalCharacter : public RCSParseError
{
  public:
    RCSIllegalCharacter(const char *myvalue)
    {
      value = myvalue;
    };
    virtual ~RCSIllegalCharacter() throw() {};
};

class RCSExpected : public RCSParseError
{
  public:
    string got;
    string wanted;
    RCSExpected(const char *mygot, const char *mywanted)
    {
      got = mygot;
      wanted = mywanted;
    };
    RCSExpected(const char *mygot, const char c)
    {
      got = mygot;
      wanted = c;
    };
    virtual ~RCSExpected() throw() {};
};

class rcstoken
{
 public:
  size_t length, size, delta;
  char *data;

 public:
  rcstoken(const char *mydata, size_t mylen)
  {
    init(mydata, mylen);
  };
  rcstoken(const char *mydata)
    {
      init(mydata, strlen(mydata));
    };
  rcstoken(size_t mysize = DEFAULT_TOKEN_SIZE,
           size_t mydelta = DEFAULT_TOKEN_DELTA)
    {
      data = NULL;
      size = mysize;
      length = 0;
      delta = mydelta;
    };
  ~rcstoken()
    {
      if (data)
        free(data);
      data = NULL;
    };
  void init(const char *mydata, size_t mylen);
  int null_token()
    {
      return data == NULL;
    };
  rcstoken& operator=(const char b)
    {
      grow(2);
      length = 1;
      data[0] = b;
      data[1] = 0;

      return *this;
    };
  rcstoken& operator+=(const char b)
    {
      append(b);

      return *this;
    };
  rcstoken& operator+=(rcstoken& token)
    {
      append(token);

      return *this;
    };
  int operator==(const char *b)
    {
      size_t b_len;
      return data && b && length == (b_len = strlen(b)) &&
        memcmp(data, b, (b_len<length) ? b_len : length) == 0;
    };
  int operator!=(const char *b)
    {
      return (! (*this == b));
    };
  int operator==(const char b)
    {
      return (length == 1) && data && (*data == b);
    };
  int operator!=(const char b)
    {
      return (! (*this==b));
    };
  char operator[](size_t i)
    {
      return data[i];
    };
  void append(const char *b, size_t b_len);
  void append(const char b)
    {
      grow(length+2);
      data[length] = b;
      data[length++] = 0;
    };
  void append(rcstoken& token)
    {
      append(token.data, token.length);
    };
  void grow(size_t new_size);
  rcstoken *copy_begin_end(size_t begin, size_t end);
  rcstoken *copy_begin_len(size_t begin, size_t len);
};

typedef list<rcstoken> tokenlist;
typedef tokenlist::iterator tokenlist_iter;



/* This class is a handler that receive the event generated by the parser
   i.e.: When we reach the head revision tag, etc... */
class Sink
{
  public:
    Sink() {};
    virtual ~Sink() throw () {};
    virtual void set_head_revision(rcstoken &revision) = 0;
    virtual void set_principal_branch(rcstoken &branch_name) = 0;
    virtual void define_tag(rcstoken &name, rcstoken &revision) = 0;
    virtual void set_comment(rcstoken &comment) = 0;
    virtual void set_description(rcstoken &description) = 0;
    virtual void define_revision(rcstoken &revision, long timestamp,
                                 rcstoken &author, rcstoken &state,
                                 tokenlist &branches, rcstoken &next) = 0;
    virtual void set_revision_info(rcstoken &revision,
                                   rcstoken &log, rcstoken &text) = 0;
    virtual void tree_completed() = 0;
    virtual void parse_completed() = 0;
};

/* The class is used to get one by one every token in the file. */
class TokenParser
{
  private:
    istream *input;
    char buf[CHUNK_SIZE];
    int buflength;
    int idx;
    rcstoken *backget;
  public:
    rcstoken *get(int allow_eof);
    void unget(rcstoken *token);
    int eof()
    {
      return (input->gcount() == 0);
    };
    void match(const char *token)
    {
      auto_ptr<rcstoken> ptr(get(FALSE));
      if (*ptr != token)
        throw RCSExpected(ptr->data, token);
    }
    void match(const char c)
    {
      auto_ptr<rcstoken> token(get(FALSE));

      if ((*token) != c)
        throw RCSExpected(token->data, c);
    };
    TokenParser(istream *myinput)
    {
      input = myinput;
      backget = NULL;
      idx = 0;
      input->read(buf, CHUNK_SIZE);
      if ( (buflength = input->gcount()) == 0 )
        throw RCSParseError("Non-existing file or empty file");
    };
    ~TokenParser()
    {
      if (input != NULL)
      {
        delete input;
        input = NULL;
      };
      if (backget != NULL)
      {
        delete backget;
        backget = NULL;
      };
    };
};

/* this is the class that does the actual job: by reading each part of
   the file and thus generate events to a sink event-handler*/
class tparseParser
{
  private:
    TokenParser *tokenstream;
    Sink *sink;
    void parse_rcs_admin();
    void parse_rcs_tree();
    void parse_rcs_description();
    void parse_rcs_deltatext();
  public:
    tparseParser(istream *myinput, Sink* mysink)
    {
      sink = mysink;
      tokenstream = new TokenParser(myinput);
    }
    void parse()
    {
      parse_rcs_admin();
      parse_rcs_tree();

      // many sinks want to know when the tree has been completed so they can
      // do some work to prepare for the arrival of the deltatext
      sink->tree_completed();

      parse_rcs_description();
      parse_rcs_deltatext();
      // easiest for us to tell the sink it is done, rather than worry about
      // higher level software doing it.
      sink->parse_completed();
    }
    ~tparseParser()
    {
      delete tokenstream;
      delete sink;
    }
};

#endif /* __PARSE_H */
