// 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 "ClangPragmaHandler.h"

void ClangPragmaHandler::HandlePragma(clang::Preprocessor &PP, clang::PragmaIntroducer Introducer,
                  clang::Token &PragmaTok) {
  // consume all directive tokens
  clang::Token Tok;
  PP.Lex(Tok);
  if (Tok.isNot(clang::tok::identifier)) {
    p1_.err() << sev_error << Tok.getLocation()
      << "#pragma acxx must be followed by a valid identifier" << endMessage;
    return;
  }

  string command = PP.getSpelling(Tok);
  if (command == "affect" || command == "filter") {
    PP.Lex(Tok);
    if (Tok.isNot(clang::tok::string_literal)) {
      p1_.err() << sev_error << Tok.getLocation()
        << "string literal with filename pattern expected after #pragma acxx " << command
        << endMessage;
      return;
    }
    string pattern = PP.getSpelling(Tok);
    pattern = pattern.substr(1, pattern.length() - 2); // discard double quotes
    string regex = to_regex_syntax(pattern, '^', '$');
    if (regex[0] != '^') {
      p1_.err() << sev_error << Tok.getLocation() << regex << endMessage;
      return;
    }

    PP.Lex(Tok);
    if (Tok.isNot(clang::tok::eod)) {
      p1_.err() << sev_error << Tok.getLocation()
        << "unexpected tokens after #pragma acxx " << command << endMessage;
      while(Tok.isNot(clang::tok::eod))
        PP.Lex(Tok);
      return;
    }

    if (command == "affect") {
      string file = p1_.source_unit(ACToken(Tok)).name();
      if (file.length() > 2 && file.substr(file.length() - 3) == ".ah") {
        deps_.push_back({ file, pattern, regex });
      }
      else {
        p1_.err() << sev_error << PragmaTok.getLocation()
          << "#pragma acxx affect only allowed in aspect header" << endMessage;
      }
    }
    else /* if (command == "filter") */ {
      // hand-over the filter pattern as a regular expression string to the phase 1 parser
      p1_.add_advice_filter(regex);
    }
  }
  else {
    p1_.err() << sev_error << Tok.getLocation()
      << "invalid command string '" << command << "' used in #pragma acxx"
      << endMessage;
    return;
  }
}

string ClangPragmaHandler::to_regex_syntax(const string &in, char prefix, char suffix) {
  ostringstream out;
  out << prefix;
  int len = in.length();
  // if the first character is not '/' (root path) and if the pattern does not
  // begin with "**/" already, inject "([^/]*/)*"
  if (!in.empty() && in.substr(0,1) != "/" && in.substr(0,3) != "**/") {
      out << "([^/]*/)*";
  }
  bool path_wildcard_allowed = true;
  bool sep_allowed = true;
  int pos = 0;
  string error_msg;
  while (pos < len) {
    if (in[pos] == '/') {
      if (!sep_allowed) {
        error_msg = "two path separators without path in between";
        break;
      }
      if (pos == len - 1) {
        error_msg = "path separator at end of filename pattern";
        break;
      }
    }
    if (in.substr(pos, 2) == "**") {
      if (!path_wildcard_allowed) {
        error_msg = "double path wildcard '**'";
        break;
      }
      if (in.substr(pos, 3) != "**/") {
        error_msg = "path wildcard '**' not follow by '/'";
        break;
      }
      out << "([^/]*/)*/"; // match path sequence: [^/]* => character sequence without '/'
      pos += 3;
      path_wildcard_allowed = false;
      sep_allowed = false;
      continue;
    }

    // escape character that have special meanings in regular expressions
    sep_allowed = (in[pos] != '/');
    switch (in[pos]) {
      case '*':
        out << "[^/]*"; // match a sequence of non-path-separator characters
        break;
      case '?':
        out << "[^/]";  // match a single non-path-separator character
        break;
      case '.':         // handle characters with special meaning in ECMA regular expressions
      case '+':
      case '[':
      case ']':
      case '(':
      case ')':
      case '^':
      case '$':
      case '\\':
        out << "\\";    // escape the character
      default:          // fallthrough
        out << in[pos]; // default: just copy the character
    }
    pos++;
    path_wildcard_allowed = true;
  }

  if (error_msg.empty()) { // no error detected
    out << suffix;
    // cout << "regex: |" << out.str() << "|" << endl;
  }
  else {
    out.clear(); out.str("");
    out << error_msg << " in column " << (pos + 1) << " of \"" << in << "\"";
  }
  return out.str();
}
