// This file is part of the AspectC++ compiler 'ac++'.
// Copyright (C) 1999-2003  The 'ac++' developers (see aspectc.org)
//                                                                
// This program is free software;  you can redistribute it and/or 
// modify it under the terms of the GNU General Public License as 
// published by the Free Software Foundation; either version 2 of 
// the License, or (at your option) any later version.            
//                                                                
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of 
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the  
// GNU General Public License for more details.                   
//                                                                
// You should have received a copy of the GNU General Public      
// License along with this program; if not, write to the Free     
// Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, 
// MA  02111-1307  USA                                            

#include "PointCutExprParser.h"
#include "PointCutExpr.h"
#include "PointCutSearcher.h"
#include "ACConfig.h"

#include <exception>
#include <string>
#include <map>
using namespace std;

// TODO: exceptions cause memory leak; objects on the heap are not freed
struct PCEParserException : exception {
  string _what;
  PCEParserException(const string &s) : _what(s) {}
  ~PCEParserException() throw() {}
  const char* what() const throw() { return _what.c_str (); }
};

enum PctTokType {
  PCTTOK_NONE,
  PCTTOK_EXEC,
  PCTTOK_CALL,
  PCTTOK_BUILTIN,
  PCTTOK_GET,
  PCTTOK_SET,
  PCTTOK_REF,
  PCTTOK_ALIAS,
  PCTTOK_CONSTRUCTION,
  PCTTOK_DESTRUCTION,
  PCTTOK_CLASSES,
  PCTTOK_WITHIN,
  PCTTOK_MEMBER,
  PCTTOK_BASE,
  PCTTOK_DERIVED,
  PCTTOK_THAT,
  PCTTOK_TARGET,
  PCTTOK_ARGS,
  PCTTOK_RESULT,
  PCTTOK_CFLOW,
  PCTTOK_MATCHEXPR,
  PCTTOK_OPEN,
  PCTTOK_CLOSE,
  PCTTOK_OR,
  PCTTOK_AND,
  PCTTOK_NOT,
  PCTTOK_COMMA,
  PCTTOK_COLON_COLON,
  PCTTOK_ID
};

static map<string, PctTokType> pct_funcs = {
  { "execution", PCTTOK_EXEC },
  { "call", PCTTOK_CALL },
  { "builtin", PCTTOK_BUILTIN },
  { "get", PCTTOK_GET },
  { "set", PCTTOK_SET },
  { "ref", PCTTOK_REF },
  { "alias", PCTTOK_ALIAS },
  { "construction", PCTTOK_CONSTRUCTION },
  { "destruction", PCTTOK_DESTRUCTION },
  { "classes", PCTTOK_CLASSES },
  { "within", PCTTOK_WITHIN },
  { "member", PCTTOK_MEMBER },
  { "base", PCTTOK_BASE },
  { "derived", PCTTOK_DERIVED },
  { "that", PCTTOK_THAT },
  { "args", PCTTOK_ARGS },
  { "target", PCTTOK_TARGET },
  { "result", PCTTOK_RESULT },
  { "cflow", PCTTOK_CFLOW }
};

// A local string matcher class, specialized for the token types that we have here.
class SMatch {
  const char *start_;
  const char *end_;
  PctTokType id_;
  bool conf_data_jps_;
  bool conf_builtin_;
  PctTokType match_expr();
  PctTokType twice(char c, PctTokType id);
  PctTokType name();
public:
  SMatch (const char *str, const ACConfig &conf) : start_(str), end_(str), id_(PCTTOK_NONE) {
    conf_data_jps_ = conf.data_joinpoints();
    conf_builtin_  = conf.builtin_operators();
  }
  void lookup();
  PctTokType id() const { return id_; }
  string str() const { return string(start_, end_ - start_); }
  bool at_end() const { return *end_ == '\0'; }
};

PctTokType SMatch::match_expr() {
  end_++; // skip '"'
  while (*end_) {
    if (*end_ == '\"') {
      end_++;
      return PCTTOK_MATCHEXPR;
    }
    end_++; // skip valid character
  }
  return PCTTOK_NONE; // in case of error
}

PctTokType SMatch::twice(char c, PctTokType id) {
  end_++;
  if (*end_ == c) {
    end_++;
    return id;
  }
  return PCTTOK_NONE; // in case of error
}

PctTokType SMatch::name() {
  char c = *end_;
  if ((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || c == '_') {
    end_++;
    c = *end_;
    while ((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9') || c == '_') {
      end_++;
      c = *end_;
    }
    std::string s(start_, end_ - start_);
    auto result = pct_funcs.find(s);
    if (result != pct_funcs.end()) {
      PctTokType res = result->second;
      if (!conf_builtin_ && res == PCTTOK_BUILTIN)
        return PCTTOK_ID;
      if (!conf_data_jps_ && (res == PCTTOK_GET || res == PCTTOK_SET || res == PCTTOK_REF || res == PCTTOK_ALIAS))
        return PCTTOK_ID;
      return res;
    }
    return PCTTOK_ID;
  }
  return PCTTOK_NONE; // in case of error
}

void SMatch::lookup() {
  id_ = PCTTOK_NONE; // default: no token detected
  start_ = end_; // start with the end of previous token
  switch(*end_) {
    case '\"': id_ = match_expr(); break;
    case '(': id_ = PCTTOK_OPEN; end_++; break;
    case ')': id_ = PCTTOK_CLOSE; end_++; break;
    case '|': id_ = twice('|', PCTTOK_OR); break;
    case '&': id_ = twice('&', PCTTOK_AND); break;
    case '!': id_ = PCTTOK_NOT; end_++; break;
    case ',': id_ = PCTTOK_COMMA; end_++; break;
    case ':': id_ = twice(':', PCTTOK_COLON_COLON); break;
    default: id_ = name();
  }
}

PointCutExprParser::PointCutExprParser( ACConfig &config ) : config_(config) {
}

PointCutExpr *PointCutExprParser::parse (const string &input,
    PointCutSearcher &searcher) {

  SMatch sm(input.c_str(), config_);
  sm.lookup();
  PointCutExpr *result = parse_or_expr (sm, searcher);
  if (!result)
    throw PCEParserException("Parsing failed");
  if (!sm.at_end())
    throw PCEParserException("Invalid token used");
  return result;
}

PointCutExpr *PointCutExprParser::parse_or_expr (SMatch &sm, PointCutSearcher &searcher) {

  PointCutExpr *result = parse_and_expr (sm, searcher);
  while (sm.id() == PCTTOK_OR) {
    sm.lookup();
    PointCutExpr *r = parse_and_expr (sm, searcher);
    if (!r) return 0;
    result = new PCE_Or(result, r);
  }
  return result;
}

PointCutExpr *PointCutExprParser::parse_and_expr (SMatch &sm, PointCutSearcher &searcher) {

  PointCutExpr *result = parse_unary_expr (sm, searcher);
  while (sm.id() == PCTTOK_AND) {
    sm.lookup();
    PointCutExpr *r = parse_unary_expr (sm, searcher);
    if (!r) return 0;
    result = new PCE_And(result, r);
  }
  return result;
}

PointCutExpr *PointCutExprParser::parse_unary_expr (SMatch &sm, PointCutSearcher &searcher) {

  PointCutExpr *result = parse_primary_expr (sm, searcher);
  if (result) return result;

  PointCutExpr *r = 0;
  switch ((int)sm.id()) { // typecast to 'int' to avoid warning about unhandled enumerator
  case PCTTOK_NOT:
    sm.lookup();
    r = parse_unary_expr (sm, searcher);
    if (!r) return 0;
    result = new PCE_Not (r);
    break;
  case PCTTOK_EXEC:
  case PCTTOK_CALL:
  case PCTTOK_BUILTIN:
  case PCTTOK_GET:
  case PCTTOK_SET:
  case PCTTOK_REF:
  case PCTTOK_ALIAS:
  case PCTTOK_CONSTRUCTION:
  case PCTTOK_DESTRUCTION:
  case PCTTOK_BASE:
  case PCTTOK_DERIVED:
  case PCTTOK_THAT:
  case PCTTOK_TARGET:
  case PCTTOK_CLASSES:
  case PCTTOK_WITHIN:
  case PCTTOK_MEMBER:
  case PCTTOK_ARGS:
  case PCTTOK_RESULT:
  case PCTTOK_CFLOW:
    string pct_func = sm.str();
    PctTokType tt = sm.id();
    sm.lookup();
    if (sm.id() != PCTTOK_OPEN)
      throw PCEParserException(string("Pointcut function '") + pct_func +
          "' requires opening bracket");
    sm.lookup();
    vector<PointCutExpr*> arguments;
    while (true) {
      PointCutExpr *r = parse_or_expr (sm, searcher);
      if (!r) break;
      arguments.push_back (r);
      if (sm.id() != PCTTOK_COMMA)
        break;
      sm.lookup();
    }
    if (tt != PCTTOK_ARGS && arguments.size () != 1) {
      throw PCEParserException(string("Pointcut function '") + pct_func +
          "' requires exactly one argument");
    }
    if (tt == PCTTOK_CALL)
      result = new PCE_Call (arguments[0]);
    else if (tt == PCTTOK_BUILTIN)
      result = new PCE_Builtin(arguments[0]);
    else if( tt == PCTTOK_GET )
      result = new PCE_Get( arguments[0] );
    else if( tt == PCTTOK_SET )
      result = new PCE_Set( arguments[0] );
    else if( tt == PCTTOK_REF )
      result = new PCE_Ref( arguments[0] );
    else if( tt == PCTTOK_ALIAS )
      result = new PCE_Alias( arguments[0] );
    else if (tt == PCTTOK_EXEC)
      result = new PCE_Execution (arguments[0]);
    else if (tt == PCTTOK_CONSTRUCTION)
      result = new PCE_Construction (arguments[0]);
    else if (tt == PCTTOK_DESTRUCTION)
      result = new PCE_Destruction (arguments[0]);
    else if (tt == PCTTOK_BASE)
      result = new PCE_Base (arguments[0]);
    else if (tt == PCTTOK_DERIVED)
      result = new PCE_Derived (arguments[0]);
    else if (tt == PCTTOK_THAT)
      result = new PCE_That (arguments[0]);
    else if (tt == PCTTOK_TARGET)
      result = new PCE_Target (arguments[0]);
    else if (tt == PCTTOK_CLASSES)
      result = new PCE_Classes (arguments[0]);
    else if (tt == PCTTOK_WITHIN)
      result = new PCE_Within (arguments[0]);
    else if( tt == PCTTOK_MEMBER )
      result = new PCE_Member( arguments[0] );
    else if (tt == PCTTOK_RESULT)
      result = new PCE_Result (arguments[0]);
    else if (tt == PCTTOK_CFLOW)
      result = new PCE_CFlow (arguments[0]);
    else if (tt == PCTTOK_ARGS) {
      PCE_Args *pce_args = new PCE_Args;
      for (vector<PointCutExpr*>::iterator i = arguments.begin ();
          i != arguments.end(); ++i)
        pce_args->add_arg(*i);
      result = pce_args;
    }

    if (sm.id() != PCTTOK_CLOSE)
      throw PCEParserException("Closing bracket missing");
    sm.lookup();
    break;
  }
  return result;
}

PointCutExpr *PointCutExprParser::parse_primary_expr (SMatch &sm, PointCutSearcher &searcher) {

  PointCutExpr *result = 0;
  string match_expr;
  switch ((int)sm.id()) { // typecast to 'int' to avoid warning about unhandled enumerator
  case PCTTOK_MATCHEXPR:
    do {
      match_expr += sm.str().substr(1, sm.str().size() - 2); // remove double quote at begin and end
      sm.lookup();
    } while (sm.id() == PCTTOK_MATCHEXPR);
    result = new PCE_Match(match_expr);
    if (!((PCE_Match*)result)->parse ())
      throw PCEParserException("Invalid match expression");
    break;
  case PCTTOK_OPEN:
    sm.lookup();
    result = parse_or_expr (sm, searcher);
    if (!result) return 0;
    if (sm.id() != PCTTOK_CLOSE)
      throw PCEParserException("Closing bracket missing");
    sm.lookup();
    break;
  case PCTTOK_ID:
  case PCTTOK_COLON_COLON:
    vector<string> qual_name;
    bool root_qualified = false;
    if (sm.id() == PCTTOK_COLON_COLON) {
      root_qualified = true;
      sm.lookup();
    }
    if (sm.id() != PCTTOK_ID)
      return 0;
    string id = sm.str();
    // string id (results.start, results.end);
    qual_name.push_back (id);
    sm.lookup();
    while (sm.id() == PCTTOK_COLON_COLON) {
      sm.lookup();
      if (sm.id() != PCTTOK_ID)
        return 0;
      id = sm.str();
      qual_name.push_back (id);
      sm.lookup();
    }
    if (sm.id() == PCTTOK_OPEN) {
      sm.lookup();
      ACM_Pointcut *pct_func = searcher.lookup_pct_func(root_qualified, qual_name);
      ACM_Attribute *pct_attr = searcher.lookup_pct_attr(root_qualified, qual_name);

      if(pct_func && pct_attr && config_.attributes()) {
        string msg = "Declarations of named pointcut and attribute'";
        msg += id;
        msg += "' are ambigous! ";
        throw PCEParserException(msg);
      }
      else if (!pct_func && (!pct_attr || !config_.attributes())) {
        string msg = "Named pointcut or attribute '";
        msg += id;
        msg += "' not found";
#ifdef FRONTEND_CLANG
        if(id == "builtin" || id == "get" || id == "set" || id == "ref" || id == "alias") {
          msg += ". If you meant the ";
          msg += id;
          msg += " pointcut function, then enable it by adding the '";
          if(id == "builtin") {
            msg += "--builtin_operators";
          }
          else {
            msg += "--data_joinpoints";
          }
          msg += "' command-line argument";
        }
#endif // FRONTEND_CLANG
        throw PCEParserException(msg);
      }
#ifdef FRONTEND_CLANG
      else if(config_.warn_deprecated() &&
          (id == "builtin" || id == "get" || id == "set" || id == "ref" || id == "alias")
      ) {
        config_.project().err() << Puma::sev_warning << "named pointcuts with one of the "
            "names 'alias', 'builtin', 'get', 'ref' or 'set' are deprecated as they "
            "name new predefined pointcut functions. These new poincut functions are "
            "currently disabled by default but this will change with one of the next "
            "releases." << Puma::endMessage;
      }
#endif // FRONTEND_CLANG

      if(pct_attr && config_.attributes()) {
        result = new PCE_CXX11Attr(pct_attr);
        sm.lookup();
      }
      else {
        PCE_Named *call = new PCE_Named(pct_func); // just needed for the right type
        bool first = true;
        while (sm.id() != PCTTOK_NONE && sm.id() != PCTTOK_CLOSE) {
          if (!first) {
            if (sm.id() != PCTTOK_COMMA)
              throw PCEParserException("Invalid argument list, comma expected");
            sm.lookup();
          }
          if (sm.id() != PCTTOK_ID)
            throw PCEParserException("Invalid argument list, argument name expected");
          string varname = sm.str();
          call->params().push_back(varname);
          first = false;
          sm.lookup();
        }
        if (sm.id() == PCTTOK_NONE)
          return 0;
        sm.lookup(); // consume ')'
        result = call;
      }
    }
    else {
      result = new PCE_ContextVar(id);
    }
    break;
  }
  return result;
}
