// This file is part of PUMA.
// Copyright (C) 1999-2003  The PUMA developer team.
//                                                                
// 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 <iostream>
using std::cout;
using std::endl;
#include <set>
using std::set;
#include <memory>

// FIXME: This is a hack to give ClangIntroducer access rights to the current scope
// This has to be included FIRST!
#define private public
#include "ClangIntroParser.h"
#include "clang/Sema/Sema.h"
#undef private

#include "clang/AST/ASTContext.h"
#include "clang/AST/ASTConsumer.h"
#include "clang/AST/DeclTemplate.h"
#include "clang/Sema/Template.h"
#include "clang/Sema/SemaDiagnostic.h"
#include "clang/Frontend/CompilerInstance.h"
#include "clang/Parse/Parser.h"
#include "clang/AST/Attr.h"
#include "llvm/ADT/StringExtras.h"

#include "ClangIntroducer.h"
#include "ACFileID.h"
#include "Plan.h"
#include "CodeWeaver.h"
#include "LineDirectiveMgr.h"
#include "IncludeGraph.h"
#include "Naming.h"
#include "AspectInfo.h"
#include "OrderInfo.h"
#include "IntroductionInfo.h"
#include "ModelBuilder.h"
#include "ACModel/Utils.h"
#include "PointCutContext.h"
#include "PointCutExpr.h"
#include "IntroductionUnit.h"
#include "TransformInfo.h"
#include "ClangIntroParser.h"
#include "ClangIntroSema.h"
#include "ClangAnnotation.h"
#include "ClangFileTracker.h"
#include "NamespaceAC.h"
#include "ACBase/StringUtils.h"

#include <utility> // for std::move

using namespace ACBase;

// This class defines a user-defined attribute. It is a clang feature intended for plugins.
// We use ac::attr to transport information from the introducer through the parser to the
// semantic analysis. The handleDeclAttribute is called with a pointer to the attributed
// 'Decl' node. We create a dummy clang::annotate attribute in the syntax tree with the same
// source location as the ac::attr annotation had it. With this information the model builder
// can look up the information on the annotation in the annotation map when the node is
// visited by the AST consumer.
class ACAttrInfo : public clang::ParsedAttrInfo {
  std::list<std::string> ac_attr_names_;

public:

  // set of scopes of built-in C++-11 attributes
  static std::set<std::string> builtin_attr_scopes;

  // check whether the given *normalized* full attribute name is known
  static bool is_builtin(std::string &full_name) {
    for (auto attr : getAllBuiltin())
      for (auto spelling : attr->Spellings) {
        if (full_name == spelling.NormalizedFullName)
          return true;
      }
    return false;
  }

  // check whether an attribute is built-in; name elements are normalized, i.e.
  // for global, gnu, and clang namepaces __<name>__ is transformed to <name>
  static bool is_builtin(std::vector<std::string> &name_elements) {
    assert(name_elements.size() > 0);
    bool normalize = (name_elements.size() == 1 || (name_elements.size() == 2 &&
      (name_elements[0] == "gnu" || name_elements[0] == "clang" || name_elements[0] == "_Clang" ||
      name_elements[0] == "__gnu__" || name_elements[0] == "__clang__")));
    std::string full_name;
    bool first = true;
    for (auto &name : name_elements) {
      if (!first)
        full_name += "::";
      if (normalize && first && name == "_Clang")
        name = "clang";
      else if (normalize && name.find("__") == 0 && name.substr(2).find("__") == name.length() - 4)
        name = name.substr(2, name.length() - 4);
      full_name += name;
      first = false;
    }
    return is_builtin(full_name);
  }

  ACAttrInfo() {
    OptArgs = 0;

    // determine the set of built-in attribute namespaces
    for (auto attr : getAllBuiltin()) {
      for (auto spelling : attr->Spellings) {
        string fullname (spelling.NormalizedFullName);
        auto scope_end_pos = fullname.find("::");
        if (scope_end_pos != fullname.npos) {
          builtin_attr_scopes.insert(fullname.substr(0, scope_end_pos));
        }
      }
    }
    builtin_attr_scopes.insert("_Clang"); // equivalent to "clang"

    // we need the list of string objects to make sure that the char array are permanent
    ac_attr_names_.push_back("__ac_attr");
    ac_attr_names_.push_back("__ac::__ac_attr");
    for (auto &scope : builtin_attr_scopes)
      ac_attr_names_.push_back(scope + "::__ac_attr");
    
    Spelling *s = new Spelling[ac_attr_names_.size()];
    int i = 0;
    for (auto &attr_name : ac_attr_names_) {
      s[i].Syntax = clang::ParsedAttr::AS_CXX11;
      s[i].NormalizedFullName = attr_name.c_str();
      i++;
    }
    Spellings = llvm::ArrayRef(s, ac_attr_names_.size());

  }

  bool diagAppertainsToDecl(clang::Sema &S, const clang::ParsedAttr &Attr,
                            const clang::Decl *D) const override {
    return true;
  }

  AttrHandling handleDeclAttribute(clang::Sema &S, clang::Decl *D,
                                   const clang::ParsedAttr &Attr) const override {
#if CLANG_VERSION_NUMBER >= VERSION_NUMBER_18_1_3
    clang::AnnotateAttr *anno_attr = clang::AnnotateAttr::Create(D->getASTContext(), Attr.getAttrName()->getName(), Attr);
#else
    clang::AnnotateAttr *anno_attr = clang::AnnotateAttr::Create(D->getASTContext(), Attr.getAttrName()->getName(),Attr.getRange());
#endif
    D->addAttr(anno_attr);
    return AttributeApplied;
  }

#if CLANG_VERSION_NUMBER >= VERSION_NUMBER_20_1_2
  bool diagAppertainsToStmt(clang::Sema &S, const clang::ParsedAttr &Attr,
                            const clang::Stmt *St) const override {
    return true;
  }

  AttrHandling handleStmtAttribute(clang::Sema &S, clang::Stmt *St, const clang::ParsedAttr &Attr,
                                   class clang::Attr *&Result) const override {
    Result = clang::AnnotateAttr::Create(S.Context, Attr.getAttrName()->getName(), Attr);
    return AttributeApplied;
  }
#endif
};

// set of scopes of built-in C++-11 attributes
std::set<std::string> ACAttrInfo::builtin_attr_scopes;

clang::ParsedAttrInfoRegistry::Add<ACAttrInfo> Z("__ac_attr", "internal representation of an AspectC++ annotation");

ClangIntroducer::ClangIntroducer(Plan &pl, CodeWeaver &ccw, ModelBuilder &jpm,
                IncludeGraph &ig, ACConfig &c, PosHints &pos_hints, clang::CompilerInstance *ci)
    : _plan(pl), _code_weaver(ccw), _jpm(jpm), _ig(ig), _conf(c), _ci(ci), _pos_hints(pos_hints), _parser(0) {
  clang::Preprocessor &PP = _ci->getPreprocessor();
  // FIXME: hack: const removed; we have to modify the returned token.
  PP.setTokenWatcher([this](const clang::Token &tok) { this->lex((clang::Token &)tok); });

  // inject the AC namespace and forward declarations of all known aspects
  string ac_builtins = NamespaceAC::def(_conf);
  ProjectModel::Selection all_aspects;
  // get all aspects from the join point model
  _jpm.select (JPT_Aspect, all_aspects);

    // remember that these aspects should become friend of all classes
  string aspect_fwd_decls = "\n";
  for (ProjectModel::Selection::iterator iter = all_aspects.begin ();
      iter != all_aspects.end (); ++iter) {
    ACM_Aspect &jpl = (ACM_Aspect&)**iter;
    aspect_fwd_decls += "class ";
    aspect_fwd_decls += jpl.get_name();
    aspect_fwd_decls += ";\n";
  }
  ac_builtins += aspect_fwd_decls;

  _file_tracker = new ClangFileTracker(_conf, ac_builtins); // PP is responsible for delete!
  PP.addPPCallbacks(std::unique_ptr<clang::PPCallbacks>(_file_tracker));
}

// Destructor: release all allocated resources
ClangIntroducer::~ClangIntroducer () {
}

void ClangIntroducer::update_macro_pos(const clang::Token& tok) {
  if (tok.getLocation().isMacroID()) {
    clang::Preprocessor &PP = _ci->getPreprocessor();
    clang::SourceManager &SM = PP.getSourceManager();
    clang::SourceLocation next_exp_loc = SM.getExpansionLoc(tok.getLocation());
    if (_macro_pos != -1 && _exp_loc == next_exp_loc)
      _macro_pos++;
    else
      _macro_pos = 0;
    _exp_loc = next_exp_loc;
  }
  else
    _macro_pos = -1;
}

void ClangIntroducer::internal_lex (clang::Token &tok) {
  clang::Preprocessor &PP = _ci->getPreprocessor();
  PP.Lex (tok);
#if CLANG_VERSION_NUMBER >= VERSION_NUMBER_18_1_3
  if (PP.IsPreviousCachedToken(tok))
#else  
  if (PP.isBacktrackEnabled())
#endif
    PP.ReplacePreviousCachedToken({});
}

void ClangIntroducer::lex (clang::Token &tok) {

  if (_token_watcher_disabled)
    return;

  while (true) {
    update_macro_pos(tok);
    clang::SourceLocation loc = tok.getLocation();
    clang::Preprocessor &PP = _ci->getPreprocessor();
    clang::SourceManager &SM = PP.getSourceManager();
    if (loc.isMacroID()) {
      loc = SM.getExpansionLoc(loc);
    }
    if (!_pos_hints.empty() && _macro_pos == _pos_hints.front().macro_pos() && loc == _pos_hints.front().loc()) {
      cout << "size: " << _pos_hints.size() << endl;
      _pos_hints.pop_front();
      cout << "HINT reached!" << endl;
    }
    else if (_pos_hints.size() > 0)
      cout << loc.printToString(SM) << " " << _pos_hints.front().loc().printToString(SM) << " "
      << _macro_pos << " " << _pos_hints.front().macro_pos() << endl;

    if (tok.is(clang::tok::eof)) {
      if (_have_eof) {
  //      cout << "EOF after code injection" << endl;
        // workaround: clang warns if skipped inline functions are used. In our case
        // this happens with inline functions that are not part of the project.
        // To avoid annoying messages they are suppressed here.
        // TODO: really needed?
        _ci->getSema().getDiagnostics().setSeverity(clang::diag::warn_undefined_inline,
            clang::diag::Severity::Ignored, clang::SourceLocation());
      }
      else {
        _have_eof = true;
        // handle the end of the translation unit, e.g. non-inline intros.
        _token_watcher_disabled = true;
        tunit_end(tok);
        _token_watcher_disabled = false;
      }
      return;
    }

    _token_watcher_disabled = true;
    // normal case: not EOF

    if (_file_tracker->in_project()) {
      // scan macro expansions and store the for later use during code transformation
      _code_weaver.collect_macro_token(_ci->getPreprocessor(), tok);
    }

  //    if (!tok.isAnnotation())
  //      cout << "Token: " << _ci->getPreprocessor().getSpelling(tok) << endl;
  //    else
  //      cout << "Anno" << endl;

    if (_conf.attributes()) {
      // handle annotations on the token stream; they have to be parsed, saved, and transformed
      annotation (tok);
    }

    if (_conf.project().tricore_hack()) {
      // skip "asm [volatil](...)", because register names are not known to clang
      skip_asm(tok);
    }

    if (_file_tracker->ac_keywords_enabled()) {
      if (aspect_def(tok) ||
          advice_def(tok) ||
          pointcut_def(tok) ||
          (_conf.attributes() && attribute_def (tok)))
        continue;
    }

    _token_watcher_disabled = false;
    class_intro(tok);
    break;
  }
}

clang::TagDecl *get_tag_from_parser (clang::Parser &parser) {
  clang::DeclContext *dc = parser.getCurScope()->getEntity();
  if (dc) {
    clang::TagDecl *td = llvm::dyn_cast<clang::TagDecl>(dc);
    if (td)
      return td;
  }
  return nullptr;
}

void ClangIntroducer::class_intro(clang::Token &first) {

  if (_next_is_start) {
    _next_is_start = false;
    clang::TagDecl *td = get_tag_from_parser(*_parser);
    if (td && (_tag_intros.empty() || _tag_intros.top().tag() != td)) {
      _tag_intros.push(IntroContext(_depth, td));
      class_start(_tag_intros.top(), _last_lbrace_loc);
    }
  }

  clang::Preprocessor &PP = _ci->getPreprocessor();
  clang::SourceManager &SM = PP.getSourceManager();
  switch (first.getKind()) {
  case clang::tok::l_brace:
    if (SM.getBufferName(first.getLocation()) == "<hack>") // FIXME: not very elegant
      break;
    if (_in_def) {
      _next_is_start = true;
      _last_lbrace_loc = first.getLocation();
      _in_def = false;
    }
    _depth++;
    break;
  case clang::tok::r_brace:
    if (!_tag_intros.empty() && _tag_intros.top().depth() == _depth) {
      if (class_end(_tag_intros.top(), first))
        _depth++;
      else
        _tag_intros.pop();
    }
    _depth--;
    break;
  case clang::tok::semi:
    _in_def = false; // "class Foo;" is no class definition
    break;
  case clang::tok::kw_class: // note: 'aspect' has already been replace by 'class'
  case clang::tok::kw_struct:
  case clang::tok::kw_enum:
  case clang::tok::kw_union:
    _in_def = true;
    break;
  default:
    return;
  }
//  cout << "depth " << depth << endl;
}

// Skip "asm [volatile](...)" on tricore, because this Clang doesn't know the register names
void ClangIntroducer::skip_asm(clang::Token &first) {
  if (first.isNot(clang::tok::kw_asm))
    return;
  bool syntax_error = true;
  list<clang::Token> reinject_tokens;
  clang::Token tok;
  internal_lex(tok);
  reinject_tokens.push_back(tok);
  if (tok.is(clang::tok::kw_volatile)) {
    internal_lex(tok);
    reinject_tokens.push_back(tok);
  }
  if (tok.is(clang::tok::l_paren)) {
    int parens = 1;
    while (parens > 0 && tok.isNot(clang::tok::eof)) {
      internal_lex(tok);
      reinject_tokens.push_back(tok);
      if (tok.is(clang::tok::r_paren))
        parens--;
      else if (tok.is(clang::tok::l_paren))
        parens++;
    }
    if (parens == 0)
      syntax_error = false;
  }

  if (syntax_error)
    inject_tokens(reinject_tokens);
  else
    internal_lex(first);
}

void ClangIntroducer::annotation(clang::Token &first) {

  // TODO: check whether the current file belongs to the project (or not?)

  // If using the annotation starts with 'using <ns>:', we perform a tranformation
  // of <ns> into "__ac" if the <ns> is not a known built-in attribute namespace,
  // such as "gnu", "__clang__", etc.. We also allow for nested namespace names here,
  // such as "X::Y::Z".
  // If the namespace transformation has been performed, all attributes must be
  // user-defined. Therefore, the whole sequence of attributes will be removed from
  // the source code (for the back-end compiler).
  // All user-defined attributes are replaced by __ac_attr. The collected information
  // including the true name is store in a map, addressed by the source location.
  // For the back-end compiler all user-defined attributes are removed.

  clang::Preprocessor &PP = _ci->getPreprocessor();

  if (first.isNot(clang::tok::l_square)) {
    if (_options_changed) {
      // the first bracket enables C++11 mode in order to parse "[[" and "constexpr" successfully
      // here we revert that setting
      _ci->getLangOpts () = _original_lang_options;
      if (!_original_lang_options.CPlusPlus11) {
        clang::IdentifierInfo *ii_const_expr = PP.getIdentifierInfo("constexpr");
        ii_const_expr->revertTokenIDToIdentifier();
      }
      _options_changed = false;
    }
    _brackets = 0;
    return;
  }

  if (_brackets == 0) {
    // store the location of the first '[' for later
    _seq_start_loc = first.getLocation();
    // make sure the clang parser accepts the annotation even if we are in C++98
    if (!_ci->getLangOpts().CPlusPlus11) {
      _original_lang_options = _ci->getLangOpts();
      _ci->getLangOpts ().DoubleSquareBracketAttributes = 1;
      _ci->getLangOpts ().CPlusPlus11 = 1;
      clang::IdentifierInfo *ii_const_expr = PP.getIdentifierInfo("constexpr");
      ii_const_expr->revertIdentifierToTokenID(clang::tok::kw_constexpr);
      _options_changed = true;
    }
    // switch to next state
    _brackets++;
    return;
  }

  clang::Token tok;
  list<clang::Token> reinject_tokens;
  std::list<AnnotationMap::iterator> annotation_iters;
  std::vector<std::string> namespace_elements;
  internal_lex(tok);
  if (tok.is(clang::tok::kw_using)) {
    reinject_tokens.push_back(tok); // the using token will be unchanged in the token stream
    internal_lex(tok);
    reinject_tokens.push_back(tok); // what ever the next token is, it is re-injected (normally ns-name)
    if (tok.isNot(clang::tok::identifier)) {
      inject_tokens(reinject_tokens); // reinject invalid inspected token
      _brackets = 0;
      return;
    }
    string root_ns = PP.getSpelling(tok);
    namespace_elements.push_back(root_ns); // save the namespace (root)
    bool ns_is_builtin = (ACAttrInfo::builtin_attr_scopes.find(root_ns) != ACAttrInfo::builtin_attr_scopes.end());
    internal_lex(tok);
    if (tok.is(clang::tok::coloncolon) || !ns_is_builtin) { // AspectC++ extension: nested namespace allowed
      reinject_tokens.back().setIdentifierInfo(&PP.getIdentifierTable().get("__ac")); // change namespace to '__ac'
    }
    while (tok.is(clang::tok::coloncolon)) {
      internal_lex(tok);
      if (tok.isNot(clang::tok::identifier)) {
        inject_tokens(reinject_tokens); // reinject invalid inspected token
        _brackets = 0;
        return; // syntax error
      }
      namespace_elements.push_back(PP.getSpelling(tok)); // save the namespace element, don't re-inject
      internal_lex(tok);
    }
    reinject_tokens.push_back(tok); // the token will be unchanged in the token stream, normally ':'
    if (tok.isNot(clang::tok::colon)) {
      // reinject scoped name?
      inject_tokens(reinject_tokens); // reinject invalid inspected token
      _brackets = 0;
      return;
    }
    internal_lex(tok);
  }
  bool in_seq = true;
  bool is_user_defined_seq = true;
  while (in_seq) {
    // create and initialize an annotation object
    Annotation attr;
    attr.checked = false;
    attr.attribute = 0;
    attr.seqBegin = _seq_start_loc;
    for (auto &scope_name : namespace_elements)
      attr.attrNames.push_back(scope_name);
    // parse annotation
    std::vector<clang::Token> name_tokens;
    while (true) {
      if (!tok.getIdentifierInfo()) // we can't use 'tok.isNot(clang::tok::identifier)', because it might be the same as a keyword, e.g. "const"
        break;
      attr.attrNames.push_back(PP.getSpelling(tok));
      name_tokens.push_back(tok);
      internal_lex(tok);
      if (tok.isNot(clang::tok::coloncolon))
        break;
      name_tokens.push_back(tok);
      internal_lex(tok);
    }

    if (attr.attrNames.size() > 0) {
      // determine whether this is a special AspectC++ annotation or built-in
      attr.is_user_defined = !ACAttrInfo::is_builtin(attr.attrNames);

      // handle reinjection of the right name tokens.
      if (attr.is_user_defined) {
        // AspectC++ annotations are turned into '__ac_attr' (user-defined, see above).
        // If the annotation starts with "[[ using <namespace> : ...", the attribute will become
        // <namespace>::__ac_attr, which is okay, because __ac_attr exists in all built-in
        // attribute namespaces!
        reinject_tokens.push_back(name_tokens[0]);
        reinject_tokens.back().setIdentifierInfo(&PP.getIdentifierTable().get("__ac_attr"));
      }
      else {
        // just copy collected tokens unchanged
        for (auto nt : name_tokens)
          reinject_tokens.push_back(nt);
        // there is at least one standard attribute in the annotation sequence
        is_user_defined_seq = false;
      }

      clang::Token &first_name_tok = name_tokens[0];
      attr.tokBegin = first_name_tok.getLocation();
      if (tok.isNot(clang::tok::l_paren) || !attr.is_user_defined)
        reinject_tokens.push_back(tok);
      if (tok.is(clang::tok::l_paren)) {
        unsigned paren_count = 1;
        string param;
        do {
          internal_lex(tok);
          if (!attr.is_user_defined)
            reinject_tokens.push_back(tok);
          if (tok.is(clang::tok::l_paren))
            paren_count++;
          else if (tok.is(clang::tok::r_paren))
            paren_count--;
          else if (tok.is(clang::tok::comma)) {
            attr.params.push_back(param);
            param.clear();
          }
          if (paren_count > 0 && tok.isNot(clang::tok::comma)) {
            if (!param.empty())
              param += " ";
            param += PP.getSpelling(tok);
          }
          // TODO: error handling
        } while (paren_count > 0);
        if (!param.empty()) // we shouldn't push a parameter if the list was empty -- '()'
          attr.params.push_back(param);
        internal_lex(tok);
        reinject_tokens.push_back(tok);
      }
      attr.tokEnd = tok.getLocation(); // end is begin of next token => better for deleting the attribute
      if (tok.isNot(clang::tok::comma))
        in_seq = false; // make sure we terminate the loop

      // register the annotation on the annotation map, which is managed by the project model
      auto ret = _jpm.annotation_map().insert(std::pair<clang::SourceLocation, Annotation>(attr.tokBegin, attr));
      annotation_iters.push_back (ret.first);

      inject_attribute_check(_seq_start_loc, attr.tokBegin, _ci->getSema().CurContext);

      if (in_seq) {
        internal_lex(tok);
      }
    }
    else {
      reinject_tokens.push_back(tok);
      in_seq = false; // make sure we terminate the loop
    }
  }

  // here 'tok' should be the first closing square bracket and it should be already in the
  // list of 're-inject' tokens
  if (tok.is(clang::tok::r_square)) {
    clang::SourceLocation end = tok.getLocation(); // remember the position of this token
    internal_lex(tok); // fetch next token; it should be the second ']'
    reinject_tokens.push_back(tok);
    if (tok.is(clang::tok::r_square)) {
      // transform the code: user-defined attributes are deleted; potentially the whole attribute sequence
      if (is_user_defined_seq) { // delete the whole sequence : --> [[ x , y ]] <--
        _code_weaver.kill(_code_weaver.weave_pos(_seq_start_loc, WeavePos::WP_BEFORE),
            _code_weaver.weave_pos(tok.getLocation().getLocWithOffset(1), WeavePos::WP_AFTER));
      }
      else { // delete only the AspectC++ annotations
        clang::SourceLocation begin = end;
        bool have_builtin = false;
        for (std::list<AnnotationMap::iterator>::reverse_iterator i = annotation_iters.rbegin();
            i != annotation_iters.rend(); ++i) {
          Annotation &anno = (*i)->second;
          if (anno.is_user_defined) {
            begin = anno.tokBegin;
          }
          else {
            if (!have_builtin)
              begin = anno.tokEnd; // remove trailing ','
            if (begin != end)
              _code_weaver.kill(_code_weaver.weave_pos(begin, WeavePos::WP_BEFORE),
                  _code_weaver.weave_pos(end, WeavePos::WP_AFTER));
            begin = end = anno.tokBegin;
            have_builtin = true;
          }
        }
        if (begin != end)
          _code_weaver.kill(_code_weaver.weave_pos(begin, WeavePos::WP_BEFORE),
              _code_weaver.weave_pos(end, WeavePos::WP_AFTER));
      }
    }
  }

  // re-inject tokens that we had to inspect and omit the ones that are filtered-out
  inject_tokens(reinject_tokens);

  _brackets = 0;
  return;
}

bool ClangIntroducer::attribute_def(clang::Token &first) {

  if (first.isNot(clang::tok::identifier))
    return false;

  clang::Preprocessor &PP = _ci->getPreprocessor();
  if (PP.getSpelling(first) != "attribute")
    return false;

  // at this point we are sure to the AspectC++ keyword "attribute"
  first.setKind(clang::tok::kw_int); // now it can be parsed

  list<clang::Token> reinject_tokens;

  // now fetch the next token: should be the name of the attribute
  clang::Token tok;
  internal_lex(tok);
  if (tok.is(clang::tok::identifier)) {
//    cout << "attribute " << PP.getSpelling(tok) << " found" << endl;

    // add the require prefix to the name in order to put attributes into their own "namespace"
    std::string prefix = "__ac_attr1_";
    tok.setIdentifierInfo(PP.getIdentifierInfo(prefix + PP.getSpelling(tok)));
    reinject_tokens.push_back(tok);

    // find the end of the declaration for deletion
    do {
      internal_lex(tok);
      reinject_tokens.push_back(tok);
    } while (tok.isNot(clang::tok::semi) && tok.isNot(clang::tok::eof));

    if (tok.is(clang::tok::semi)) {
      // Delete the function declaration from the code that the back-end compiler will see
      _code_weaver.kill (_code_weaver.weave_pos(first.getLocation(), WeavePos::WP_BEFORE),
          _code_weaver.weave_pos(tok.getEndLoc(), WeavePos::WP_AFTER));
    }
  }
  else
    reinject_tokens.push_back(tok);

  // re-inject tokens that we had to inspect
  inject_tokens(reinject_tokens);

  return true; // replaced 'attribute' by 'int'
}

int _advice_no = 0; // FIXME: This should be a class attribute!

bool ClangIntroducer::aspect_def(clang::Token &first) {
  if (first.isNot(clang::tok::identifier))
    return false;

  clang::Preprocessor &PP = _ci->getPreprocessor();
  if (PP.getSpelling(first) != "aspect")
    return false;

  // Okay, 'aspect' keyword detected. Replace by 'class'.
  first.setKind(clang::tok::kw_class);

  // Also make sure that the source code will be transformed before saved
  const WeavePos &from = _code_weaver.weave_pos (first.getLocation(), WeavePos::WP_BEFORE);
  const WeavePos &to   = _code_weaver.weave_pos (first.getEndLoc(), WeavePos::WP_AFTER);
  _code_weaver.replace(from, to, "class");

  _advice_no = 0; // reset advice counter
  return true;
}

bool ClangIntroducer::advice_def(clang::Token &first) {
  if (first.isNot(clang::tok::identifier))
    return false;

  clang::Preprocessor &PP = _ci->getPreprocessor();
  if (PP.getSpelling(first) != "advice")
    return false;

  clang::Token tok;

  // skip pointcut expression; syntax has been checked by phase 1 parser already!
  while (true) {
    internal_lex(tok);
    if (tok.is(clang::tok::colon))
      break;
  }

  internal_lex(tok);
  if (tok.is(clang::tok::identifier) && PP.getSpelling(tok) == "order") {
    // ** order advice **
    // skip order declaration tokens until we eventually find ';'
    while (true) {
      internal_lex(tok);
      if (tok.is(clang::tok::semi))
        break;
    }
    // delete the order declaration from code
    const WeavePos &from = _code_weaver.weave_pos (first.getLocation(), WeavePos::WP_BEFORE);
    const WeavePos &to   = _code_weaver.weave_pos (tok.getEndLoc(), WeavePos::WP_AFTER);
    _code_weaver.kill(from, to);

    // replace first token by the new next token
    internal_lex(first);
  }
  else if (tok.is(clang::tok::identifier) &&
      (PP.getSpelling(tok) == "before" || PP.getSpelling(tok) == "after" || PP.getSpelling(tok) == "around")) {
    // ** code advice **

    bool needs_jp_obj = false, needs_jp_type = false;
    list<clang::Token> start_tokens, arg_tokens;
    list<clang::Token> body_tokens;
    start_tokens.push_back(tok);
    ostringstream advice_name;
    advice_name << "__a" << _advice_no << "_" << PP.getSpelling(tok);
    _advice_no++;
    start_tokens.back().setIdentifierInfo(&PP.getIdentifierTable().get(advice_name.str()));
    internal_lex(tok);
    start_tokens.push_back(tok); // opening '(' of argument list
    do { // locate '{'; skip and record arguments and attributes if there are any
      internal_lex(tok);
      arg_tokens.push_back(tok); // push arg list including ') ... {' but not '('
    } while (tok.isNot(clang::tok::l_brace));
    // scan the advice body
    CodeWeaver::TypeUse uses_type;
    bool have_typename_kw = false;
    int level = 1;
    while (level > 0) {
      internal_lex(tok);
      body_tokens.push_back(tok);
      if (tok.is(clang::tok::kw_typename)) {
        have_typename_kw = true;
        internal_lex(tok);
        body_tokens.push_back(tok);
      }
      else {
        have_typename_kw = false;
      }
      if (tok.is(clang::tok::l_brace))
        level++;
      else if (tok.is(clang::tok::r_brace))
        level--;
      else if (tok.is(clang::tok::identifier)){
        string id = PP.getSpelling(tok);
        if (id == "tjp" || id == "thisJoinPoint") {
          needs_jp_obj = true;
          if (id == "thisJoinPoint") {
            // replace every occurrence of "thisJoinPoint" with "tjp"
            _code_weaver.replace(_code_weaver.weave_pos (tok.getLocation(), WeavePos::WP_BEFORE),
                _code_weaver.weave_pos(tok.getEndLoc(), WeavePos::WP_AFTER), "tjp");
            body_tokens.back().setIdentifierInfo(&PP.getIdentifierTable().get("tjp"));
          }
          // skip '->' token
          internal_lex(tok);
          body_tokens.push_back(tok);
          if (tok.is(clang::tok::arrow)) {
            internal_lex(tok);
            if (tok.is(clang::tok::identifier) &&
                (PP.getSpelling(tok) == "arg" || PP.getSpelling(tok) == "idx")) {
              clang::Token tmp_tok;
              internal_lex(tmp_tok);
              if (tmp_tok.is(clang::tok::less)) {
                // insert 'template' keyword
                _code_weaver.insert(_code_weaver.weave_pos(tok.getLocation(), WeavePos::WP_BEFORE), "template ");
                scan("template", "<ac-kw-inject>", body_tokens, tok.getLocation());
              }
              body_tokens.push_back(tok);
              body_tokens.push_back(tmp_tok);
            }
            else {
              body_tokens.push_back(tok);
            }
          }
        }
        // special JP-API handling if the user hasn't used the typename keyword
        else if (!have_typename_kw && id == "JoinPoint") {
          needs_jp_type = true;
          const WeavePos &from = _code_weaver.weave_pos (tok.getLocation(), WeavePos::WP_BEFORE);
          auto from_iter = body_tokens.end(); --from_iter; // last element in the list (token 'JoinPoint')
          // check whether the next token is '::'
          internal_lex(tok);
          body_tokens.push_back(tok);
          if (tok.is(clang::tok::coloncolon)) {
            internal_lex(tok);
            bool have_template_kw = false;
            if (tok.is(clang::tok::kw_template)) {
              body_tokens.push_back(tok);
              have_template_kw = true;
              internal_lex(tok);
            }
            if (tok.is(clang::tok::identifier)) {
              string name = PP.getSpelling(tok);
              if( name == "That" || name == "Result" || name == "Target" ||
                  name == "Entity" || name == "MemberPtr" || name == "Array" ) {
                ostringstream tn;
                Naming::tjp_typedef(tn, name.c_str ());
                _code_weaver.replace (from, _code_weaver.weave_pos(tok.getEndLoc(), WeavePos::WP_AFTER), tn.str ());
                tok.setIdentifierInfo(&PP.getIdentifierTable().get(tn.str()));
                if (have_template_kw)
                  body_tokens.pop_back(); // remove 'template token'
                body_tokens.pop_back(); // remove '::' token
                body_tokens.pop_back(); // remove 'JoinPoint' token
                if (name == "That")
                  uses_type.that = true;
                else if (name == "Target")
                  uses_type.target = true;
                else if (name == "Result")
                  uses_type.result = true;
                else if (name == "Entity")
                  uses_type.entity = true;
                else if (name == "MemberPtr")
                  uses_type.memberptr = true;
                else if( name == "Array" )
                  uses_type.array = true;
              }
              else if( name == "Arg" || name == "Dim" ) {
                // generate "template" keyword in front of Arg<i> or Dim<i> if the user hasn't provided it
                if (!have_template_kw) {
                  _code_weaver.insert (_code_weaver.weave_pos(tok.getLocation(), WeavePos::WP_BEFORE),
                      "template ");
                  scan("template", "<ac-kw-inject>", body_tokens, tok.getLocation());
                }
                body_tokens.push_back(tok); // push 'Arg' / 'Dim'

                // TODO: implement real 'skip_template_params()' function
                do { // locate '>'
                  internal_lex(tok);
                  body_tokens.push_back(tok); // push arg list including ') ... {' but not '('
                } while (tok.isNot(clang::tok::greater));
                // End of TODO

                bool typename_needed = false;
                internal_lex(tok);
                if (tok.is(clang::tok::coloncolon)) {
                  body_tokens.push_back(tok);
                  internal_lex(tok);
                  if (tok.is(clang::tok::identifier) && PP.getSpelling(tok) != "Size") {
                    body_tokens.push_back(tok);
                    internal_lex(tok);
                    if (tok.isNot(clang::tok::coloncolon)) {
                      typename_needed = true;
                    }
                  }
                }
                else
                  typename_needed = true; // just 'Dim<X>' or 'Arg<Y>'
                if (typename_needed) {
                  // This must be a type -> insert 'typename' keyword
                  _code_weaver.insert (from, "typename ");
                  list<clang::Token> tmp_toks;
                  scan("typename", "<ac-kw-inject>", tmp_toks, tok.getLocation());
                  body_tokens.insert(from_iter, tmp_toks.back());
                }
              }
            }
            body_tokens.push_back(tok);
          }
        }
      }
    }

    // re-inject tokens that we had to inspect. Omit the skipped tokens
    inject_tokens(body_tokens);

    if( uses_type.any() ) {
      // also generate some typedefs for That, Target, and Result
      // the corresponding typedef must replace each JoinPoint::(That|Target|Result)
      ostringstream typedefs;
      typedefs << endl;
      if( uses_type.that ) {
        typedefs << "  typedef typename JoinPoint::That ";
        Naming::tjp_typedef (typedefs, "That");
        typedefs << ";" << endl;
      }
      if( uses_type.target ) {
        typedefs << "  typedef typename JoinPoint::Target ";
        Naming::tjp_typedef (typedefs, "Target");
        typedefs << ";" << endl;
      }
      if( uses_type.result ) {
        typedefs << "  typedef typename JoinPoint::Result ";
        Naming::tjp_typedef (typedefs, "Result");
        typedefs << ";" << endl;
      }
      if( uses_type.entity ) {
        typedefs << "  typedef typename JoinPoint::Entity ";
        Naming::tjp_typedef (typedefs, "Entity");
        typedefs << ";" << endl;
      }
      if( uses_type.memberptr ) {
        typedefs << "  typedef typename JoinPoint::MemberPtr ";
        Naming::tjp_typedef (typedefs, "MemberPtr");
        typedefs << ";" << endl;
      }
      if( uses_type.array ) {
        typedefs << "  typedef typename JoinPoint::Array ";
        Naming::tjp_typedef (typedefs, "Array");
        typedefs << ";" << endl;
      }

      inject(typedefs.str(), "<tjp-types>", body_tokens.front().getLocation());
      _code_weaver.insert(_code_weaver.weave_pos (arg_tokens.back().getEndLoc(), WeavePos::WP_AFTER),
          typedefs.str());
    }

    inject_tokens(arg_tokens);

    if (needs_jp_obj) {
      string tjp_decl = "JoinPoint *tjp";
      if (arg_tokens.front().isNot(clang::tok::r_paren))
        tjp_decl += ", ";
      inject(tjp_decl, "<tjp-arg>", arg_tokens.front().getLocation());
      _code_weaver.insert(_code_weaver.weave_pos (arg_tokens.front().getLocation(), WeavePos::WP_BEFORE),
          tjp_decl);
    }

    inject_tokens(start_tokens);

    ostringstream prefix;
    prefix << "public: ";
    if (needs_jp_obj || needs_jp_type)
      prefix << "template <typename JoinPoint> ";
    // handle 'inline' and '__attribute__((always_inline))' here
    if (_conf.data_joinpoints() || _conf.builtin_operators()) {
      // FIXME: ideally the compiler should make this decision
      //          alternatively the programmer should specify the attribute manually
      // only if experimental features (data_joinpoints or builtin_ops) are active:
      // always inline the generic advice code, as it is join-point specific anyway
      if (_code_weaver.problems()._use_always_inline) // GNU extension
        prefix << "__attribute__((always_inline)) ";
      prefix << "inline ";
    }

    prefix << "void ";
    inject(prefix.str(), "<advice-prefix>", start_tokens.front().getLocation());
    // _code_weaver.insert(_code_weaver.weave_pos (first.getLocation(), WeavePos::WP_BEFORE),
    //     prefix.str() + advice_name.str());

    // delete code up to this point (including before/after/around)
    const WeavePos &from = _code_weaver.weave_pos (first.getLocation(), WeavePos::WP_BEFORE);
    const WeavePos &to   = _code_weaver.weave_pos (start_tokens.front().getEndLoc(), WeavePos::WP_AFTER);
    // _code_weaver.kill(from, to);

    _code_weaver.replace(from, to, prefix.str() + advice_name.str());

    // replace first token by the new next token
    internal_lex(first);
  }
  return true;
}


bool ClangIntroducer::pointcut_def(clang::Token &first) {
  if (first.isNot(clang::tok::identifier))
    return false;

  clang::Preprocessor &PP = _ci->getPreprocessor();
  if (PP.getSpelling(first) != "pointcut")
    return false;

  // Okay, 'pointcut' keyword detected. Replace by 'void'.
  first.setKind(clang::tok::kw_void);

  list<clang::Token> reinject_tokens;
  clang::Token tok;

  // now fetch and keep all tokens until we find '='; syntax has been checked already
  while (true) {
    internal_lex(tok);
    if (tok.is(clang::tok::equal))
      break;
    reinject_tokens.push_back(tok);
  }
  // if we have a pure virtual pointcut ("... = 0;"), we keep " = 0"
  clang::Token eq_tok = tok;
  internal_lex(tok);
  if (tok.isLiteral() && PP.getSpelling(tok) == "0") {
    reinject_tokens.push_back(eq_tok);
    reinject_tokens.push_back(tok);
  }
  // skip token until we find ';'
  while (tok.isNot(clang::tok::semi)) {
    internal_lex(tok);
  }
  // ';' is not skipped
  reinject_tokens.push_back(tok);

  // re-inject tokens that we had to inspect. Omit the skipped tokens
  inject_tokens(reinject_tokens);

  // Also make sure that the source code will be transformed before saved:
  // The whole pointcut definition is deleted.
  const WeavePos &from = _code_weaver.weave_pos (first.getLocation(), WeavePos::WP_BEFORE);
  const WeavePos &to   = _code_weaver.weave_pos (tok.getEndLoc(), WeavePos::WP_AFTER);
  _code_weaver.kill(from, to);

  return true; // 'pointcut' changed to 'void'
}

static const char *prot_str (clang::AccessSpecifier prot) {
  switch (prot) {
  case clang::AS_private: return "AC::PROT_PRIVATE";
  case clang::AS_protected: return "AC::PROT_PROTECTED";
  case clang::AS_public: return "AC::PROT_PUBLIC";
  case clang::AS_none: return "AC::PROT_NONE";
  }
  return 0;
}

static string spec_str (bool is_static, bool is_mutable, bool is_virtual) {
  int pos = 0;
  string result;
  if (is_static) {
    if (pos++) result += "|";
    result += "AC::SPEC_STATIC";
  }
  if (is_mutable) {
    if (pos++) result += "|";
    result += "AC::SPEC_MUTABLE";
  }
  if (is_virtual) {
    if (pos++) result += "|";
    result += "AC::SPEC_VIRTUAL";
  }
  if (!pos)
    result = "AC::SPEC_NONE";
  return result;
}

// insert friend declarations for all aspects
bool ClangIntroducer::insert_aspectof_function(std::string &str,
                                          clang::CXXRecordDecl *ci) {
  if (!ci)
    return false;

  ACM_Aspect *jpl_aspect = _jpm.register_aspect (ci);
  if (!jpl_aspect)
    return false;

  // iterate over all member functions and find any definitions of
  // 'aspectof' or 'aspectOf'
  clang::NamedDecl *aspectOf_func = 0;
  clang::NamedDecl *aspectof_func = 0;
  for (clang::CXXRecordDecl::decl_iterator di = ci->decls_begin(),
                                           de = ci->decls_end();
       di != de; ++di) {
    clang::NamedDecl *nd = llvm::dyn_cast<clang::NamedDecl> (*di);
    if (!nd)
      continue;
    if (nd->getKind () == clang::Decl::FunctionTemplate ||
        nd->getKind () == clang::Decl::CXXMethod) {
      if (nd->getNameAsString() == "aspectof")
        aspectof_func = nd;
      else if (nd->getNameAsString() == "aspectOf")
        aspectOf_func = nd;
    }

    // Register all members now. Some may not be in the model yet because we're
    // in the middle of parsing classes.
    clang::CXXMethodDecl *md = llvm::dyn_cast<clang::CXXMethodDecl> (nd);
    if (md) {
      if (!_jpm.register_pointcut(md, jpl_aspect))
        _jpm.register_function(md, jpl_aspect);
    }
  }

  // Don't create instances of abstract aspects.
  if (::is_abstract(*jpl_aspect))
    return false;

#if 0
  if (aspectOf_func && !aspectof_func) {
    // rename the function to 'aspectof'
    CT_FctDef *fctdef = (CT_FctDef*)aspectOf_func->Tree ();
    CT_SimpleName *name = ((CT_Declarator*)fctdef->Declarator ())->Name ();
    name = name->Name (); // if it is qualified
    const WeavePos &name_start =
      _code_weaver.weave_pos (ACToken (name->token ()), WeavePos::WP_BEFORE);
    const WeavePos &name_end =
      _code_weaver.weave_pos (ACToken (name->end_token ()), WeavePos::WP_AFTER);
    _code_weaver.replace (name_start, name_end, "aspectof");
    return false; // nothing to introduce
  }
#endif

  if (!aspectOf_func && !aspectof_func) {
    llvm::raw_string_ostream unit(str);
    unit << '\n'
      << "public:" << '\n'
      << "  static " << ci->getName () << " *aspectof () {" << '\n'
      << "    static " << ci->getName () << " __instance;" << '\n'
      << "    return &__instance;" << '\n'
      << "  }" << '\n'
      << "  static " << ci->getName () << " *aspectOf () {" << '\n'
      << "    return aspectof ();" << '\n'
      << "  }" << '\n'
      << "private:" << '\n';
    return true;
  }
  return false;
}

bool ClangIntroducer::insert_aspect_friend_decls(std::string &str,
                                            clang::Decl *decl) {
  if (!decl)
    return false;

  clang::TagDecl *td = llvm::dyn_cast<clang::TagDecl>(decl);
  // introductions into unions require some extra effort (not implemented yet)
  if (td && td->isUnion())
    return false;

  // check whether the insertion is indicated for this class or union
  clang::SourceManager &sm = _code_weaver.getRewriter().getSourceMgr();

  if (clang::CXXRecordDecl *r = llvm::dyn_cast<clang::CXXRecordDecl>(decl)) {
    // no need for template instances; the templates already get the friend injection
    if (r->getTemplateSpecializationKind() ==
            clang::TSK_ImplicitInstantiation ||
        r->getTemplateSpecializationKind() ==
            clang::TSK_ExplicitInstantiationDefinition)
      return false;
    // C-like structs don't need friend declarations, because they can be accessed anyway
    if (r->isCLike())
      return false;
  }

  clang::NamedDecl* nd = llvm::dyn_cast<clang::NamedDecl>(decl);
  if (nd) {
    std::string qual_name(nd->getQualifiedNameAsString());
    llvm::StringRef name(nd->getName());

    // code introduced by phase 1 (in special namespace AC) is not modified here
    if (CLANG_STR_STARTS_WITH(llvm::StringRef(qual_name), "AC::"))
      return false;

    // introspection code is also not modified
    else if ( ((name == "BaseClass") ||
               (name == "Member") ||
               (name == "Function") ||
               (name == "Constructor") ||
               (name == "Destructor")) &&
               (llvm::StringRef(qual_name).count("::__TI::") ||
                llvm::StringRef(qual_name).count("::__TJP_")))
        return false;
  }

  // nested classes in template instances should also not be modified to avoid double injection
  //if (_jpm.inside_template_instance(decl))
    //return false;
  // the class has to belong to the project
  llvm::StringRef name = sm.getPresumedLoc(decl->getLocation()).getFilename();
  llvm::StringRef buffer_name = sm.getBufferName(decl->getLocation());
  if (!CLANG_STR_STARTS_WITH(buffer_name, "<intro") &&
      !name.empty() && !_jpm.get_project().is_below(name.str()))
    return false;

  // OK, perform the insertion ...

  // get all aspects from the join point model
  ProjectModel::Selection all_aspects;
  _jpm.select (JPT_Aspect, all_aspects);

  // generate the list of aspects
  // also make sure that an aspect is not friend of itself!
  bool result = false;
  for (ProjectModel::Selection::iterator iter = all_aspects.begin ();
       iter != all_aspects.end (); ++iter) {
    ACM_Aspect &jpl = (ACM_Aspect&)**iter;
    if (!(td && td->getQualifiedNameAsString() == jpl.get_name())) {
      str += "  friend class ::";
      str += jpl.get_name();
      str += ";\n";
      result = true;
    }
  }

  return result;
}

static bool is_attribute(clang::DeclaratorDecl *dd) {
  if (clang::FieldDecl *fd = llvm::dyn_cast<clang::FieldDecl>(dd))
    return !fd->isBitField();
  return true;
}

// insert introspection code
//  * at the end of class definitions, after AspectC++ introductions
std::string ClangIntroducer::insert_introspection_code(clang::CXXRecordDecl *cd,
                                                       int precedence) {
  std::string str;
  llvm::raw_string_ostream unit(str);

  if (!cd /*|| !cd->Object ()->DefObject ()->ClassInfo()*/)
    return "";

  // return if this class is not an introduction target or has C linkage
  if (!_jpm.is_valid_model_class (cd) || !_jpm.is_intro_target (cd))
    return "";

  // introspection templates cannot be declared inside of a local class
  if (cd->isLocalClass()) // integrate check into _jpm.is_valid_model_class ?
    return "";
  
  // Return if this class is defined in an extern "C" context
  if (TI_Class::is_extern_c(cd))
    return "";
//  if (clang::LinkageSpecDecl *SD =
//          llvm::dyn_cast<clang::LinkageSpecDecl>(cd->getDeclContext()))
//    if (SD->getLanguage() == clang::LinkageSpecDecl::lang_c)
//      return "";

  unit << "public:\n";
  unit << "  struct ";
  if (precedence == -1)
    unit << "__TI";
  else
    unit << "__TJP_" << precedence;
  unit << " {\n";

  // generate the signature (fully qualified target class name)
  unit << "    static const char *signature () { return \"";
  unit << cd->getQualifiedNameAsString();
  unit << "\"; }\n";

  // generate a 32-bit hash code from the fully qualified target class name
  unit << "    enum { HASHCODE = "
       << ACBase::hash(cd->getQualifiedNameAsString()) << "U };\n";

  // generate a typedef for the target type
  unit << "    typedef " << *cd << " That;\n";

  // generate a list with all base classes
  unit << "    template<int I, int __D=0> struct BaseClass {};\n";
  unsigned b = 0;
  for (clang::CXXRecordDecl::base_class_iterator I = cd->bases_begin(),
                                                 E = cd->bases_end();
       I != E; ++I) {
    unit << "    template <int __D> struct BaseClass<" << b
         << ", __D> { typedef ";
    unit << TI_Type::get_type_text(I->getType(), &cd->getASTContext(), "Type", TSEF_ENABLE, true, TSEF_DONOTCHANGE, false, true, false);
    unit << "; ";
    unit << "static const AC::Protection prot = "
         << prot_str(I->getAccessSpecifierAsWritten()) << "; ";
    unit << "static const AC::Specifiers spec = (AC::Specifiers)("
          << spec_str (false, false, I->isVirtual()) << "); ";
    unit << "};\n";
    b++;
  }
  unit << "    enum { BASECLASSES = " << b << " };\n";

  llvm::StringRef clsname = cd->getName();
  // generate Member<I> => a list with all attribute types
  unit << "    template<int I, int __D=0> struct Member {};\n";
  unsigned e = 0;
  for (clang::DeclContext::decl_iterator I = cd->decls_begin(),
                                         E = cd->decls_end();
       I != E; ++I) {
    clang::DeclaratorDecl *dd = llvm::dyn_cast<clang::DeclaratorDecl>(*I);
    if (!dd || !is_attribute(dd))
      continue;

    // Here anonymous struct/class/union members are ignored
    // TODO: Handle all members of these anonymous constructs. They are visible
    //       in the enclosing scope, i.e. here!
    string name = dd->getNameAsString ();
    if (name.empty ())
      continue;

    // static members are represented as VarDecls, the rest is FieldDecl.
    clang::VarDecl *vd = llvm::dyn_cast<clang::VarDecl>(dd);
    clang::FieldDecl *fd = llvm::dyn_cast<clang::FieldDecl>(dd);
    if (!vd && !fd)
      continue;

    unit << "    template <int __D> struct Member<" << e << ", __D> { typedef ";
    unit << TI_Type::get_type_text(dd->getType(), &dd->getASTContext(), "Type", TSEF_ENABLE, true, TSEF_DONOTCHANGE, false, true, false);
    unit << "; typedef AC::Referred<Type>::type ReferredType; ";
    unit << "static const AC::Protection prot = " << prot_str(dd->getAccess())
         << "; ";
    unit << "static const AC::Specifiers spec = (AC::Specifiers)("
         << spec_str(vd, fd && fd->isMutable(), false) << "); ";

    // generate pointer => the typed pointer to attribute I
    unit << "static ReferredType *pointer (" << clsname
         << " *obj = 0) { return (ReferredType*)&";
    if (vd)
      unit << clsname << "::" << *dd;
    else
      unit << "obj->" << *dd;
    unit << "; } ";
    // generate member_name => the name of attribute i
    unit << "static const char *name () { return \"" << *dd << "\"; }\n";
    unit << " };\n";
    unit << "    typedef Member<" << e << "> Member_" << dd->getName() << ";\n";
    e++;
  }
  unit << "    enum { MEMBERS = " << e << " };\n";

  // generate a list with all member functions
  unit << "    template<int I, int __D=0> struct Function {};\n";
  unit << "    template<int I, int __D=0> struct Constructor {};\n";
  unit << "    template<int I, int __D=0> struct Destructor {};\n";
  unsigned int functions=0, constructors=0, destructors=0;
  for (clang::CXXRecordDecl::method_iterator I = cd->method_begin(),
                                             E = cd->method_end();
       I != E; ++I) {
    clang::CXXMethodDecl *fi = *I;

    if (!fi || !_jpm.is_valid_model_function(fi) || !fi->isUserProvided())
      continue;

    if (llvm::dyn_cast<clang::CXXConstructorDecl>(fi)) {
      unit << "    template <int __D> struct Constructor<" << constructors << ", __D> { ";
      constructors++;
    }
    else if (llvm::dyn_cast<clang::CXXDestructorDecl>(fi)) {
      unit << "    template <int __D> struct Destructor<" << destructors << ", __D> { ";
      destructors++;
    }
    else {
      unit << "    template <int __D> struct Function<" << functions << ", __D> { ";
      functions++;
    }

    unit << "static const AC::Protection prot = " << prot_str (fi->getAccess ()) << "; ";
    unit << "static const AC::Specifiers spec = (AC::Specifiers)("
          << spec_str (false, false, fi->isVirtual()) << "); ";

    unit << "};\n";
  }
  unit << "    enum { FUNCTIONS = " << functions << " };\n";
  unit << "    enum { CONSTRUCTORS = " << constructors << " };\n";
  unit << "    enum { DESTRUCTORS = " << destructors << " };\n";

  unit << "  };\n";
  if (precedence == -1)
    unit << "  typedef __TI __TI_" << cd->getName() << ";\n";
#if 0
  // paste a #line directive
  LineDirectiveMgr &lmgr = _code_weaver.line_directive_mgr ();
  ACUnit *dunit = new ACUnit (err);
  lmgr.directive (*dunit, inspos.unit (), inspos);
  *dunit << endu;
  if (dunit->empty ()) delete dunit; else _code_weaver.insert (pos, dunit);
#endif
  return unit.str();
}


ACM_Class *ClangIntroducer::create_plan (clang::Decl *decl) {
  
  clang::RecordDecl *ci = llvm::dyn_cast<clang::RecordDecl>(decl);
  if (!ci)
    return 0;

  // return if this class is not an introduction target
  if (!_jpm.is_intro_target (ci))
    return 0;

  // try to register this class (might be a newly introduced class)
  ACM_Class *jpl = _jpm.register_aspect (ci);
  if (!jpl) jpl = _jpm.register_class (ci);

  // return if this is either not a valid model class or no valid intro target
  if (!jpl || !jpl->get_intro_target ())
    return 0;
  
  // iterate through all introduction advice in the plan
  PointCutContext context (_jpm);
  const list<IntroductionInfo*> &intros = _plan.introduction_infos ();
  for (list<IntroductionInfo*>::const_iterator i = intros.begin ();
       i != intros.end (); ++i) {
    IntroductionInfo *intro = *i;
    // TODO: consider stand-alone advice here as well in the future (C-mode)
    // something like ... if (!intro->is_activated ()) continue;
    context.concrete_aspect (intro->aspect ());
    Binding binding;     // binding and condition not used for intros
    Condition condition;
    PointCutExpr *pce = (PointCutExpr*)intro->pointcut_expr ().get ();
    if (pce->match (*jpl, context, binding, condition)) {
      // Check whether the joinpoint is filtered-out by #pragma acxx filter
      bool filter_out = false;
      for (auto source : joinpoint_shadow(*jpl)) {
        if (TI_Introduction::of(intro->intro())->is_filtered_out(*source)) {
          filter_out = true;
          break;
        }
      }
      // consider the class for the introduction if it was not filtered out
      if (!filter_out)
        _plan.consider (*jpl, intro);
    }
  }

  if (jpl->has_plan ()) {
    // order the advice & check
    _plan.order (jpl);

    // remember the class info and join point location
    _clangtargets.insert (ClangTargetMap::value_type (ci, jpl));
  }
  return jpl;
}


ACM_Class *ClangIntroducer::plan_lookup (clang::Decl *ci) {
  // Lazily create the plan.
  if (_seen_classes.insert(ci).second)
    create_plan (ci);

  ClangTargetMap::iterator i = _clangtargets.find (ci);
  if (i != _clangtargets.end () && i->second->has_plan())
    return i->second;
  return 0;
}

const clang::Decl *ClangIntroducer::link_once_object (clang::CXXRecordDecl *ci) {
  clang::SourceManager &sm = _code_weaver.getRewriter().getSourceMgr();
  // Functions before fields.
  for (clang::CXXRecordDecl::decl_iterator DI = ci->decls_begin(),
                                           DE = ci->decls_end();
       DI != DE; ++DI) {
    // Skip generated code.
    llvm::StringRef name = sm.getBufferName((*DI)->getLocation());
    if (CLANG_STR_STARTS_WITH(name, "<intro"))
      continue;

    if (clang::CXXMethodDecl *MD = llvm::dyn_cast<clang::CXXMethodDecl>(*DI)) {
      // skip template functions and built-in functions
      // they don't need link-once code
      if (!MD->isUserProvided())
        continue;
      // if a member function is undefined it is link-once code!
      if (!MD->isDefined())
        return MD;
      // if the function is defined, outside the class scope, and is not inline,
      // we found the implementation
      const clang::FunctionDecl *def;
      if (MD->getBody(def) && def->getLexicalParent() != ci &&
          !def->isInlineSpecified())
        return def;
    }
  }

  for (clang::CXXRecordDecl::decl_iterator DI = ci->decls_begin(),
                                           DE = ci->decls_end();
       DI != DE; ++DI) {
    // Skip generated code.
    llvm::StringRef name = sm.getBufferName((*DI)->getLocation());
    if (CLANG_STR_STARTS_WITH(name, "<intro"))
      continue;

    // Look for uninitialized static members with out of line definition.
    if (clang::VarDecl *VD = llvm::dyn_cast<clang::VarDecl>(*DI))
      if (VD->isStaticDataMember() && !VD->getInit() && VD->getDefinition())
        return VD;
  }
  return 0;
}

void ClangIntroducer::parse (clang::Parser *parser, const std::string &source_code,
                        clang::SourceLocation loc, clang::DeclContext *context,
                        bool force_cxx11) {
//  cout << "parsing '" << source_code << "'" << endl;

  bool force_global_scope = (context == 0);
  string source_code_str = source_code + ":";
  // parse in C++ 11 mode if demanded
  clang::LangOptions &lang_options = (clang::LangOptions&)_parser->getPreprocessor().getLangOpts();
  clang::LangOptions original_lang_options = lang_options;
  clang::IdentifierInfo *ii_const_expr = _parser->getPreprocessor().getIdentifierInfo("constexpr");
  if (force_cxx11 && !original_lang_options.CPlusPlus11) {
    lang_options.CPlusPlus11 = 1;
    ii_const_expr->revertIdentifierToTokenID(clang::tok::kw_constexpr);
  }

  // if not demanded otherwise, parse the string in the global scope
  if (force_global_scope)
    context = _ci->getASTContext().getTranslationUnitDecl ();
  clang::Token &token = (clang::Token &)parser->getCurToken();
  clang::Token saved_token = token;

  string fname = _ci->getSourceManager().getFilename(_ci->getSourceManager().getExpansionLoc(loc)).str();
  static int num = 0;
  ostringstream name;
  name << fname << "_" << num++ << "_virt";
  ACFileID vfid = _conf.project().addVirtualFile(name.str(), source_code_str);
  clang::FileID fid = _ci->getSourceManager().createFileID( vfid.file_entry(), loc, clang::SrcMgr::C_User);
#if CLANG_VERSION_NUMBER >= VERSION_NUMBER_18_1_3
  // now it gets really ugly ...
  // It is possible that we are currently scanning tokens from a macro expansion. In this case the preprocessor
  // will not allow us to include header files. However, this is necessary as introductions depend on the header
  // file with the slice definition. In order to do this without triggering assertions on Clang, we must set
  // the normal lexer (CurLexer and CurPPLexer, which is just a pointer) as well as the current token lexer (if we
  // are in a macro) to nullptr. Later we must restore the old values.
  clang::PreprocessorLexer *ppl_copy = _ci->getPreprocessor ().CurPPLexer;
  _ci->getPreprocessor ().CurPPLexer = nullptr;
  std::unique_ptr<clang::Lexer> l_copy = std::move(_ci->getPreprocessor ().CurLexer);
  std::unique_ptr<clang::TokenLexer> tl_copy = std::move(_ci->getPreprocessor ().CurTokenLexer);
  _ci->getPreprocessor ().recomputeCurLexerKind();
  _ci->getPreprocessor ().getHeaderSearchInfo().getFileInfo(vfid.file_entry()); // register as a header file
  _ci->getPreprocessor ().EnterSourceFile(fid, nullptr, loc);
#else
  _ci->getPreprocessor ().EnterSourceFile(fid, _ci->getPreprocessor().GetCurDirLookup(), loc);
#endif
  _ci->getPreprocessor ().Lex (token);
  clang::Sema &sema = _ci->getSema();
  clang::Scope *old_scope = sema.CurScope;
  sema.CurScope = force_global_scope ? sema.TUScope : parser->getCurScope();
  clang::DeclContext *old_context = sema.CurContext;
  clang::DeclContext *old_lex_context = sema.OriginalLexicalContext;
  _ci->getSema().CurContext = context;
  clang::Parser::DeclGroupPtrTy Result;
#if CLANG_VERSION_NUMBER >= VERSION_NUMBER_18_1_3
  clang::Sema::ModuleImportState IS = clang::Sema::ModuleImportState::NotACXX20Module;
#endif
  while (token.isNot(clang::tok::colon) &&
#if CLANG_VERSION_NUMBER >= VERSION_NUMBER_18_1_3
         !parser->ParseTopLevelDecl(Result, IS)) {
#else
         !parser->ParseTopLevelDecl(Result)) {
#endif
    // Tell the ModelBuilder about the new declarations.
    sema.getASTConsumer().HandleTopLevelDecl(Result.get());
  }

#if CLANG_VERSION_NUMBER >= VERSION_NUMBER_18_1_3
  // see comment above ...
  _ci->getPreprocessor ().CurTokenLexer = std::move(tl_copy);
  _ci->getPreprocessor ().CurLexer = std::move(l_copy);
  _ci->getPreprocessor ().CurPPLexer = ppl_copy;
  _ci->getPreprocessor ().recomputeCurLexerKind();
#endif

  sema.CurContext = old_context;
  sema.OriginalLexicalContext = old_lex_context;
  sema.CurScope = old_scope;
  token = saved_token;
  _conf.project().removeVirtualFile(vfid);

  if (force_cxx11 && !original_lang_options.CPlusPlus11) {
    ii_const_expr->revertTokenIDToIdentifier();
    lang_options = original_lang_options;
  }
}

// (re-)inject a list of tokens into the token stream
void ClangIntroducer::inject_tokens(const std::list<clang::Token> &tokens) {
  if (tokens.empty())
    return;
  std::unique_ptr<clang::Token[]> input(new clang::Token[tokens.size()]);
  int pos = 0;
  for (const clang::Token &tok: tokens)
    input[pos++] = tok;
  clang::Preprocessor &PP = _ci->getPreprocessor();
  PP.EnterTokenStream(std::move(input), tokens.size(), true, false);
}

// scan tokens from a string and append them to a token list
clang::FileID ClangIntroducer::scan(const std::string &in, const std::string &name,
    std::list<clang::Token> &tokens, clang::SourceLocation loc, bool add_eof) {
  return scan (llvm::MemoryBuffer::getMemBufferCopy(in, name), tokens, loc, add_eof);
}

// scan tokens from a memory buffer and append them to a token list
clang::FileID ClangIntroducer::scan(std::unique_ptr<llvm::MemoryBuffer> mb,
    std::list<clang::Token> &tokens, clang::SourceLocation loc, bool add_eof) {
  clang::Preprocessor &PP = _ci->getPreprocessor();
  clang::FileID fid = _ci->getSourceManager()
    .createFileID(std::move (mb), clang::SrcMgr::C_User, 0, 0, loc);
  bool Invalid;
#if CLANG_VERSION_NUMBER >= VERSION_NUMBER_12_0_0
  auto opt_mb = _ci->getSourceManager().getBufferOrNone (fid);
#if CLANG_VERSION_NUMBER >= VERSION_NUMBER_18_1_3
  Invalid = !opt_mb.has_value();
  llvm::MemoryBufferRef buff = opt_mb.value();
#else
  Invalid = !opt_mb.hasValue();
  llvm::MemoryBufferRef buff = opt_mb.getValue();
#endif
#else
  const llvm::MemoryBuffer *buff = _ci->getSourceManager().getBuffer(fid, &Invalid);
#endif
  if (!Invalid) { // invalid inclusion
//    cout << "entered:" << endl;
//#if CLANG_VERSION_NUMBER >= VERSION_NUMBER_12_0_0
//    cout << buff.getBuffer().str() << endl;
//#else
//    cout << buff->getBufferStart() << endl;
//#endif
//    cout << "--- Tokens ..." << endl;
    clang::Lexer RawLex(fid, buff, PP.getSourceManager(), PP.getLangOpts());
    RawLex.SetCommentRetentionState(false);
    RawLex.setParsingPreprocessorDirective(false);
    clang::Token RawToken;
    RawLex.LexFromRawLexer(RawToken);
    while (RawToken.isNot(clang::tok::eof)) {
      if (RawToken.is(clang::tok::hash) && RawToken.isAtStartOfLine()) {
        RawLex.setParsingPreprocessorDirective(true);
      }
      else if (RawToken.is(clang::tok::eod)) {
        RawLex.setParsingPreprocessorDirective(false);
      }
      else if (RawToken.is(clang::tok::raw_identifier)) {
        clang::IdentifierInfo *II = PP.getIdentifierInfo(RawToken.getRawIdentifier());
        if (II) {
          RawToken.setIdentifierInfo(II);
          RawToken.setKind(II->getTokenID());
        }
      }
      tokens.push_back(RawToken);
      RawLex.LexFromRawLexer(RawToken);
    }
    if (add_eof) {
      tokens.push_back(RawToken);
    }
  }
  return fid;
}

// Inject a (scanned) string into the parser.
clang::FileID ClangIntroducer::inject(const std::string &in, const std::string &name,
    clang::SourceLocation loc, bool add_eof) {
  return inject(llvm::MemoryBuffer::getMemBufferCopy(in, name), loc, add_eof);
}

clang::FileID ClangIntroducer::inject(std::unique_ptr<llvm::MemoryBuffer> mb,
    clang::SourceLocation loc, bool add_eof) {

  std::list<clang::Token> tokens;
  // scan the input in the token list
  clang::FileID fid = scan(std::move(mb), tokens, loc, add_eof);

  // now push the scanned tokens onto the token stream of the parser
  inject_tokens(tokens);
  return fid;
}

bool ClangIntroducer::class_end (IntroContext &intro_context, clang::Token &rbrace) {
  clang::TagDecl *td = intro_context.tag();
  clang::CXXRecordDecl *record_decl = llvm::dyn_cast<clang::CXXRecordDecl>(td);
  clang::SourceManager &sm = _parser->getPreprocessor().getSourceManager();
  clang::SourceLocation CurLoc = rbrace.getLocation();
  ACErrorStream &err = _conf.project().err();

//  cout << "*in* class end for " << (record_decl ? record_decl->getNameAsString() : "") << endl;
  // TODO: Is this the right place to check that it is a class/struct/aspect and not an enum/union?
  if (!record_decl) {
    intro_context.state() = IntroContext::FINISHED;
    return false;
  }

  // TODO: Also not the right place for this check!
  for (clang::TagDecl* cur = td; cur != 0; cur = llvm::dyn_cast_or_null<clang::TagDecl>(cur->getParent())) {
    if (cur->getName () == "__TI" || CLANG_STR_STARTS_WITH(cur->getName (), "__TJP")) {
      intro_context.state() = IntroContext::FINISHED;
      return false;
    }
    if (cur != td) break; // check one level of nesting only: skip loop after 2nd iteration
  }

  bool old = _jpm.set_disable_source_loc_registration(true);
  if (intro_context.state() == IntroContext::NEW) {
    intro_context.state() = IntroContext::IN_PROGRESS;

    intro_context.needs_introspection() = _conf.introspection(); // --introspection given?

    // base class list in the project model could not be up-to-date.
    update_base_classes (record_decl);

    // When we reach this point, we're handling intros for members. Mark this for
    // tunit_end.
    _classes_with_introduced_members.insert(record_decl);

    // first check with the plan if there are intros for this class
    ACM_Class *jpl = plan_lookup(record_decl);
    if (jpl && jpl->has_plan()) {
      intro_context.plan() = jpl->get_plan();
      TI_ClassPlan *plan = TI_ClassPlan::of(*jpl->get_plan());
      // when we reach this point first, we create a list of bufs an fill it
      // (to be handled one by one with each call to class_end()
      plan->_bufs = new std::deque<IntroductionUnit *>;
      gen_intros(jpl, *(plan->_bufs), err, td, _conf.introduction_depth());
      // handle includes for all member introductions
      if (!plan->_bufs->empty()) {
        // Fill in the IntroductionUnit structure.
        clang::FileID fid = sm.getFileID(CurLoc);
#if CLANG_VERSION_NUMBER >= VERSION_NUMBER_12_0_0
        llvm::MemoryBufferRef this_unit = sm.getBufferOrFake(fid);
#else
        const llvm::MemoryBuffer *this_unit = sm.getBuffer(fid);
#endif
        std::vector<IntroductionUnit *> &intro_units =
            get_intros_for(IntroductionUnit::cast(this_unit));
        for (IntroductionUnit *iu : *(plan->_bufs)) {
          iu->target_unit(this_unit);
          iu->location(CurLoc);
          intro_units.push_back(iu);
        }

        // Includes may be handled when introducing bases, don't do it twice.
        // We add all includes even if we don't add the member for it just yet.
        if (!_classes_with_introduced_bases.count(record_decl)) {
          // determine the units that should be included in front of the class
          set<ACFileID> units;
          ACM_ClassPlan *plan = plan_lookup(record_decl)->get_plan();
          typedef ACM_Container<ACM_MemberIntro, true> MContainer;
          for (MContainer::iterator i = plan->get_member_intros().begin();
               i != plan->get_member_intros().end(); ++i) {
            ACM_ClassSlice *cs = get_slice(*(*i)->get_intro());
            units.insert(TI_ClassSlice::of(*cs)->slice_unit());
          }
          typedef ACM_Container<ACM_BaseIntro, true> BContainer;
          for (BContainer::iterator i = plan->get_base_intros().begin();
               i != plan->get_base_intros().end(); ++i) {
            ACM_ClassSlice *cs = get_slice(*(*i)->get_intro());
            units.insert(TI_ClassSlice::of(*cs)->slice_unit());
          }

          handle_includes(units, sm, _parser, record_decl, CurLoc, err);
        }
      }
    }
  }

  // if some code needs to be injected we put it in here and inject at the end
  std::list<IntroductionUnit*> inject_units;

  if (intro_context.state() == IntroContext::IN_PROGRESS) {
    if (intro_context.plan()) {
      TI_ClassPlan *plan = TI_ClassPlan::of(*intro_context.plan());
      if (!plan->_bufs->empty()) {
        IntroductionUnit *iu = plan->_bufs->front();
        plan->_bufs->pop_front(); // remove first buffer from the list as we are handling it now

        // if needed, generate the introspection code for this introduction
        if (iu->jp_needed()) {
          std::string introspection =
              insert_introspection_code(record_decl, iu->precedence());
          if (!introspection.empty()) {
            iu->content (introspection + iu->content ());
            intro_context.needs_introspection () = true; // also generate the final introspection
          }
        }

        // Do the actual injection.
        inject_units.push_front(iu);
      }
      else {
        delete plan->_bufs;
        plan->_bufs = nullptr;
      }
    }

    if (inject_units.empty()) {
      intro_context.state() = IntroContext::FINALIZING;
      // insert introspection code for this class if the configuration option
      // --introspection was given or the Joinpoint type was used in an introduction
      // -> inserted at the end of the class -> all intros are visible
      std::string intro_text;
      if (intro_context.needs_introspection ())
        intro_text = insert_introspection_code(record_decl);

      // If this is the last insertion of a member (or there were no member intros)
      // introduce aspectof functions and friend decls.
      bool has_aspectof = insert_aspectof_function(intro_text, record_decl);
      std::string friends = "\n";
      bool has_friends = insert_aspect_friend_decls(friends, record_decl);

      if (has_friends)
        intro_text += friends;

      if (!intro_text.empty() || has_aspectof || has_friends) {

        clang::FileID fid = sm.getFileID(CurLoc);
#if CLANG_VERSION_NUMBER >= VERSION_NUMBER_12_0_0
        llvm::MemoryBufferRef this_unit = sm.getBufferOrFake(fid);
#else
        const llvm::MemoryBuffer *this_unit = sm.getBuffer(fid);
#endif

        // create the new unit for all this stuff
        IntroductionUnit *unit = new IntroductionUnit(IntroductionUnit::IU_OTHER);
        unit->target_unit(this_unit);
        unit->location(CurLoc);
        unit->intro (0); // there is no specific introduction that triggered this
        unit->content(intro_text);

        // store a pointer to the new intro unit
        std::vector<IntroductionUnit *> &intro_units =
            get_intros_for(IntroductionUnit::cast(this_unit));
        intro_units.push_back(unit);

        // Do the actual injection.
        inject_units.push_front(unit);
      }
    }

    // now inject any generated units into the token stream
    if (!inject_units.empty()) {
      clang::Preprocessor &PP = _parser->getPreprocessor();
      // push '}' (the current token) first so that it reappears after having parsed the introductions
      std::unique_ptr<clang::Token[]> input(new clang::Token[1]);
      input[0] = rbrace;
      PP.EnterTokenStream(std::move(input), 1, true, false);

      for (IntroductionUnit* iu : inject_units)
        iu->file_id(inject(iu->buffer(), CurLoc));

      // scan the first injected token and use it instead of the scanner '}'
      _token_watcher_disabled = true;
      _parser->getPreprocessor().Lex (rbrace); if (PP.isBacktrackEnabled()) PP.ReplacePreviousCachedToken({});
      _token_watcher_disabled = false;
    }
  }

  // shortcut to FINISHED state if nothing has been injected
  if (intro_context.state() == IntroContext::FINALIZING && inject_units.empty())
    intro_context.state() = IntroContext::FINISHED;

  _jpm.set_disable_source_loc_registration(old);
  return intro_context.state() != IntroContext::FINISHED;
}

void ClangIntroducer::introduce_base_classes(clang::Decl *decl,
    clang::SourceLocation CurLoc, bool first) {

  // first check with the plan if there are intros for this class
  ACM_Class *jpl = plan_lookup (decl);
  if (!jpl || !jpl->has_plan ()) {
    return;
  }

  std::deque<IntroductionUnit *> bufs;
  gen_base_intros (jpl, bufs, !first);

  if (bufs.empty())
    return;

  // Introduce bases only once.
  if (!_classes_with_introduced_bases.insert(decl).second)
    return;

  string signature = ((clang::TagDecl*)decl)->getNameAsString ();
  clang::SourceManager &sm = _parser->getPreprocessor().getSourceManager();

  set<ACFileID> units;
  ACM_ClassPlan *plan = jpl->get_plan ();
  typedef ACM_Container<ACM_MemberIntro, true> MContainer;
  for (MContainer::iterator i = plan->get_member_intros().begin();
       i != plan->get_member_intros().end(); ++i) {
    ACM_ClassSlice *cs = get_slice (*(*i)->get_intro());
    units.insert (TI_ClassSlice::of (*cs)->slice_unit ());
    }
  typedef ACM_Container<ACM_BaseIntro, true> BContainer;
  for (BContainer::iterator i = plan->get_base_intros().begin();
       i != plan->get_base_intros().end(); ++i) {
    ACM_ClassSlice *cs = get_slice (*(*i)->get_intro());
    units.insert (TI_ClassSlice::of (*cs)->slice_unit ());
    }

  ACErrorStream &err = _conf.project ().err ();
  handle_includes(units, sm, _parser, decl, CurLoc, err);

  clang::Token &token = (clang::Token &)_parser->getCurToken();
  clang::Token saved_token = token;

  inject(" {", "<hack>", CurLoc);

  clang::FileID fid = sm.getFileID(CurLoc);
#if CLANG_VERSION_NUMBER >= VERSION_NUMBER_12_0_0
  llvm::MemoryBufferRef target = sm.getBufferOrFake(fid);
#else
  const llvm::MemoryBuffer *target = sm.getBuffer(fid);
#endif
  for (std::deque<IntroductionUnit *>::reverse_iterator ui =
       bufs.rbegin (); ui != bufs.rend (); ++ui) {
    (*ui)->target_unit(target);
    (*ui)->location(CurLoc);
    (*ui)->file_id (inject ((*ui)->buffer(), CurLoc));
  }
  std::vector<IntroductionUnit *> &intro_units =
      get_intros_for(IntroductionUnit::cast(target));
  intro_units.insert(intro_units.end(), bufs.begin(), bufs.end());

  if (!first) {
    // read the comma into the current 'token'. It will be overwritten by ':'
    // after the following code injection and 'Lex' call. -> a bit tricky :-)
    _ci->getPreprocessor ().Lex (token);
    // satisfy ParseBaseClause function: code has to start with ':'
    inject(" :", "<hack>", CurLoc);
  }

  _ci->getPreprocessor ().Lex (token);
  _parser->ParseBaseClause(decl);

  token = saved_token;
  return;
}


// old version of the function: needed only for statement attributes, which cannot be user-defined
void ClangIntroducer::inject_attribute_check (clang::SourceLocation loc, const clang::AttrVec &attrs,
    clang::DeclContext *decl_context) {
  if (!_conf.attributes())
    return;

  static int attr_number = 0;
  for(const clang::Attr *attr_use : attrs) {
    AnnotationMap::iterator iter = _jpm.annotation_map().find(attr_use->getLocation());
    if (iter == _jpm.annotation_map().end())
      continue;
    Annotation &annotation = iter->second;

    // don't generate another call if this annotation has been handled already
    if (annotation.checked)
      continue;

    // For further analyses we are only interested in attribute parameters (type, value)
    if (annotation.params.size() == 0)
      continue; // go on if there is no parameter
    ostringstream intro;
    for (string param : annotation.params) {
      intro << "constexpr auto __ac_attr_" << attr_number++ << " = " << param << ";" << endl;
    }
//    cout << "intro:" << endl << intro.str();

    // parse the code on-the-fly
    _jpm.annotation_context() = &iter->second;
    bool suppress_diag = _ci->getDiagnostics().getSuppressAllDiagnostics ();
    _ci->getDiagnostics().setSuppressAllDiagnostics (true);
    parse(_parser, intro.str (), loc, decl_context, true);
    _ci->getDiagnostics().setSuppressAllDiagnostics (suppress_diag);
    ACErrorStream &err = _conf.project ().err ();
    int argnum = 0;
    for (string pval : annotation.param_values) {
      if (pval == "!E") {
        err << sev_error << annotation.tokBegin
            << "parse error in argument " << argnum << " of attribute." << endMessage;
      }
      else if (pval == "!C") {
        err << sev_error << annotation.tokBegin
            << "argument " << argnum << " of attribute is not constant." << endMessage;
      }
      argnum++;
    }
    _jpm.annotation_context() = 0;
//    cout << "intro parsing done" << endl;
  }
}

void ClangIntroducer::inject_attribute_check (clang::SourceLocation loc, clang::SourceLocation attr_loc,
    clang::DeclContext *decl_context) {
  if (!_conf.attributes())
    return;

  static int attr_number = 0;

  AnnotationMap::iterator iter = _jpm.annotation_map().find(attr_loc);
  if (iter == _jpm.annotation_map().end()) {
    cout << "fatal: attribute not found in map" << endl;
    return;
  }
  Annotation &annotation = iter->second;

  // don't generate another call if this annotation has been handled already
  if (annotation.checked)
    return;

  // For further analyses we are only interested in attribute parameters (type, value)
  if (annotation.params.size() == 0)
    return; // go on if there is no parameter
  ostringstream intro;
  for (string param : annotation.params) {
    intro << "constexpr auto __ac_attr_" << attr_number++ << " = " << param << ";" << endl;
  }
//  cout << "intro:" << endl << intro.str();

  // parse the code on-the-fly
  _jpm.annotation_context() = &iter->second;
  bool suppress_diag = _ci->getDiagnostics().getSuppressAllDiagnostics ();
  _ci->getDiagnostics().setSuppressAllDiagnostics (true);
  parse(_parser, intro.str (), loc, decl_context, true);
  _ci->getDiagnostics().setSuppressAllDiagnostics (suppress_diag);
  if (annotation.is_user_defined) {
    ACErrorStream &err = _conf.project ().err ();
    int argnum = 0;
    for (string pval : annotation.param_values) {
      if (pval == "!E") {
        err << sev_error << annotation.tokBegin
            << "parse error in argument " << argnum << " of attribute." << endMessage;
      }
      else if (pval == "!C") {
        err << sev_error << annotation.tokBegin
            << "argument " << argnum << " of attribute is not constant." << endMessage;
      }
      argnum++;
    }
  }
  _jpm.annotation_context() = 0;
}

void ClangIntroducer::class_start (IntroContext &intro_context, clang::SourceLocation lbrac) {
  // first check with the plan if there are intros for this class
  clang::CXXRecordDecl *ci = llvm::dyn_cast<clang::CXXRecordDecl>(intro_context.tag());
  if (!ci)
    return;

  // return if this class is not an introduction target
  if (!_jpm.is_intro_target (ci))
    return;

  // try to register this class (might be a newly introduced class)
  bool old = _jpm.set_disable_source_loc_registration(true);
  ACM_Class *jpl = _jpm.register_aspect (ci);
  if (!jpl) jpl = _jpm.register_class (ci);
  if (jpl) {
    // store the lbrace location for later; Clang has no method to get it.
    TI_Class::of(*jpl)->set_lbrace_loc(lbrac);

    introduce_base_classes(ci, lbrac, ci->getNumBases() == 0);
  }
  _jpm.set_disable_source_loc_registration(old);
}


// update the base classes in the project model for a class that had
// base class introductions
void ClangIntroducer::update_base_classes (clang::Decl *decl) {
  clang::CXXRecordDecl *rd = llvm::cast<clang::CXXRecordDecl>(decl);
  if (!rd || !_jpm.is_intro_target (decl)) return;
  ACM_Class *jpl = _jpm.register_aspect (rd);
  if (!jpl) jpl = _jpm.register_class (rd);
  if (!jpl) return;
  // FIXME: what about the derived-class relation? Will there be duplicates?
  jpl->get_bases().clear();
  for (clang::CXXRecordDecl::base_class_iterator I = rd->bases_begin(),
                                                 E = rd->bases_end();
       I != E; ++I) {
    clang::CXXRecordDecl *base_decl = I->getType ()->getAsCXXRecordDecl ();
    assert (base_decl);
    // re-register the base class in order to find the model element
    ACM_Class *base_jpl = _jpm.register_aspect (base_decl);
    if (!base_jpl) base_jpl = _jpm.register_class (base_decl);
    if (!base_jpl) continue;
    // insert the class into the model
    jpl->get_bases().insert(base_jpl);
    base_jpl->get_derived().insert(jpl);
    // handle this base class recursively
    update_base_classes(base_decl);
  }
}

void ClangIntroducer::handle_includes(set<ACFileID> &units,
                                 clang::SourceManager &sm,
                                 clang::Parser *parser, clang::Decl *decl,
                                 clang::SourceLocation CurLoc,
                                 ACErrorStream &err) {
  clang::FileID fid = sm.getFileID(CurLoc);
#if CLANG_VERSION_NUMBER >= VERSION_NUMBER_12_0_0
  llvm::MemoryBufferRef this_unit = sm.getBufferOrFake(fid);
#else
  const llvm::MemoryBuffer *this_unit = sm.getBuffer(fid);
#endif

  // TODO: this_unit might be a macro unit!
  // handle introductions into introduced classes (nested introductions)
  const IntroductionUnit *intro_unit = IntroductionUnit::cast (this_unit);
  if (intro_unit) this_unit = intro_unit->final_target_unit ();

//  cout << "included units for " << ci->QualName () << " in "
//       << this_unit->name () << " :" << endl;
  for (set<ACFileID>::iterator iter = units.begin ();
      iter != units.end (); ++iter) {
    ACFileID slice_unit = *iter;
    if (ACFileID::main_file(sm) == slice_unit) {
#if 0
      if (this_unit != primary)
        err << sev_error << decl->getLocation()
          << "affected by aspect in '" << slice_unit.name ().c_str ()
          << "'. Move the aspect into a separate aspect header." << endMessage;
#endif
    }
    else if (sm.getFileEntryForID(fid) &&
#if CLANG_VERSION_NUMBER >= VERSION_NUMBER_18_1_3
             _ig.includes (slice_unit, sm.getFileEntryRefForID(fid))) {
#else
             _ig.includes (slice_unit, sm.getFileEntryForID(fid))) {
#endif
      err << sev_warning << decl->getLocation()
          << "can't include '" << slice_unit.name ().c_str ()
          << "' to avoid include cycle" << endMessage;
    }
    else {
//      cout << "new edge from " << this_unit->name () << " to "
//             << slice_unit->name () << endl;

      // handling of nested classes -> search the outermost class
      clang::RecordDecl *inscls = llvm::cast<clang::RecordDecl>(decl);
      while (inscls->getLexicalParent()->isRecord())
        inscls = llvm::cast<clang::RecordDecl>(inscls->getLexicalParent());

#if CLANG_VERSION_NUMBER >= VERSION_NUMBER_18_1_3
      ACFileID insfile = sm.getFileEntryRefForID(sm.getFileID(sm.getExpansionLoc(inscls->getLocation())));
#else
      ACFileID insfile = sm.getFileEntryForID(sm.getFileID(sm.getExpansionLoc(inscls->getLocation())));
#endif
      _ig.add_edge (insfile, slice_unit);

      // namespace should be closed and re-opened
      ostringstream inc;
      _code_weaver.close_namespace (inc, inscls);

      // This is what gets parsed. We don't care about namespaces here.
        ostringstream includes;
        auto incname = _conf.project ().include_path(insfile.name (), slice_unit.name ());
        includes << endl << "#ifndef ";
      Naming::guard (includes, slice_unit);
        includes << endl << "#define ";
      Naming::guard (includes, slice_unit);
        includes << endl;
        includes << "#include \"" << incname << "\"" << endl;
        includes << "#endif" << endl;

      // re-open the namespace
      inc << includes.str();
      _code_weaver.open_namespace (inc, inscls);

      // remember the necessary code injection -> delayed until we are the end of the translation unit
      // background: "typedef struct { ... } S;" leads to a RecordDecl and a TypedefDecl. For the code
      // insertion we must find the source location of "typedef", i.e. is start location of the
      // TypedefDecl. However, here this node hasn't been created, yet. Hence the postponement.
      _delayed_injections.push_back(std::pair<clang::RecordDecl*, std::string>(inscls, inc.str()));

        if (_included_aspect_headers.find (slice_unit) ==
            _included_aspect_headers.end ()) {
          _included_aspect_headers.insert (slice_unit);
          parse(parser, includes.str (), CurLoc);
        }
//        else
//          cout << "'" << slice_unit->name () << "' not included again!" << endl;
      }
    }
}

bool ClangIntroducer::end_translation_unit(clang::Parser *parser) {

  bool injection_done = false;
  for (std::set<clang::Decl *>::iterator
           i = _classes_with_introduced_members.begin(),
           e = _classes_with_introduced_members.end();
       i != e; ++i) {
    clang::Decl *decl = *i;
  
    // Introduce members only once.
    if (!_classes_with_introduced_non_inlines.insert(decl).second)
      continue;
  
    // first check with the plan if there are intros for this class
    ACM_Class *jpl = plan_lookup (decl);
    if (!jpl || !jpl->has_plan ()) {
      continue;
    }
  
    clang::TagDecl *tag_decl = (clang::TagDecl*)decl;
    //const clang::Type* tag_type = tag_decl->getTypeForDecl();
    //string signature = tag_decl->getNameAsString ();
    //cout << "END_TU " << signature << " "
    //  << tag_type->getCanonicalTypeInternal().getAsString() << endl;
  
    std::deque<IntroductionUnit *> bufs;
    ACErrorStream &err = _conf.project ().err ();
    gen_intros (jpl, bufs, err, tag_decl, _conf.introduction_depth (), true);

    if (bufs.empty())
      continue;

    clang::SourceLocation CurLoc = parser->getCurToken().getLocation();
    clang::SourceManager &sm = parser->getPreprocessor().getSourceManager();

    if (sm.getFileID(tag_decl->getLocation()) != sm.getMainFileID()) {
      const clang::Decl *loo =
          link_once_object(llvm::dyn_cast<clang::CXXRecordDecl>(tag_decl));
      if (!loo)
        continue;
      else if (const clang::DeclContext *dc =
                   llvm::dyn_cast<clang::DeclContext>(loo))
        if (dc->getLexicalParent() == tag_decl)
      continue;
      // Returned fields are always link once objects.
    }

    // determine the units that should be included in front of the intros
    set<ACFileID> units;
    ACM_ClassPlan *plan = jpl->get_plan ();
    typedef ACM_Container<ACM_MemberIntro, true> MContainer;
    for (MContainer::iterator i = plan->get_member_intros().begin();
         i != plan->get_member_intros().end(); ++i) {
      ACM_ClassSlice *cs = get_slice (*(*i)->get_intro());
      TI_ClassSlice::of(*cs)->set_in_link_once_tunit(true);
      std::vector<ACFileID> &member_units = TI_ClassSlice::of(*cs)->non_inline_member_units();
      for (std::vector<ACFileID>::iterator i = member_units.begin ();
          i != member_units.end (); ++i)
        units.insert (*i);
    }
    
    // parse the aspect headers that are needed by this intro
    for (set<ACFileID>::iterator iter = units.begin ();
      iter != units.end (); ++iter) {
      ACFileID slice_unit = *iter;
      ACFileID primary_unit = ACFileID::main_file(sm);
      if (primary_unit != slice_unit) {
//        cout << "new edge from " << primary->name () << " to "
//             << slice_unit->name () << endl;
        _ig.add_edge (primary_unit, slice_unit);
        // generate a unit with the include
        ostringstream includes;
        auto incname = _conf.project().include_path(primary_unit.name(), slice_unit.name());
        includes << endl << "#ifndef ";
        Naming::guard (includes, slice_unit);
        includes << endl << "#define ";
        Naming::guard (includes, slice_unit);
        includes << endl;
        includes << "#include \"" << incname << "\"" << endl;
        includes << "#endif" << endl;
        string inc (includes.str ());
        _code_weaver.insert(
            _code_weaver.weave_pos(CurLoc, WeavePos::WP_BEFORE), inc, true);

        if (_included_aspect_headers.find (slice_unit) ==
            _included_aspect_headers.end ()) {
          _included_aspect_headers.insert (slice_unit);
          parse(parser, inc, CurLoc);
//          cout << "INCLUDE:" << inc << endl << "========" << endl;
    }
//        else
//          cout << "'" << slice_unit->name () << "' not included again!" << endl;
      }
    }

    clang::FileID fid = sm.getFileID(CurLoc);
#if CLANG_VERSION_NUMBER >= VERSION_NUMBER_12_0_0
    llvm::MemoryBufferRef this_unit = sm.getBufferOrFake(fid);
#else
    const llvm::MemoryBuffer *this_unit = sm.getBuffer(fid);
#endif
    for (std::deque<IntroductionUnit *>::reverse_iterator ui =
             bufs.rbegin();
         ui != bufs.rend(); ++ui) {
      (*ui)->target_unit(this_unit);
      (*ui)->location(CurLoc);
//      cout << "INJECT:" << endl << (*ui)->content() << endl << "========" << endl;
      (*ui)->file_id(inject((*ui)->buffer(), CurLoc, !injection_done));
      injection_done = true;
    }
    std::vector<IntroductionUnit *> &intro_units =
        get_intros_for(IntroductionUnit::cast(this_unit));
    intro_units.insert(intro_units.end(), bufs.begin(), bufs.end());
  }
  return injection_done;
}

void ClangIntroducer::tunit_end (clang::Token &tok) {

  // re-enter the eof token
  std::unique_ptr<clang::Token[]> TokCopy(new clang::Token[1]);
  TokCopy[0] = tok;
  _ci->getPreprocessor ().EnterTokenStream(std::move(TokCopy), 1, true, false);

  end_translation_unit(_parser); // inject code and parse required headers

  // lex and return the first token (either eof or the first injected token)
  _ci->getPreprocessor ().Lex (tok);

  // finally execute delayed code insertation for header files needed in introduced code
  for (auto &injection : _delayed_injections) {
    clang::RecordDecl *inscls = injection.first;
    std::string &inc = injection.second;

    // inspos should be infront of the class
    clang::SourceLocation inspos = CLANG_GET_LOC_START(inscls);

    // if the class definition is enclosed in a typedef, use the start position of the typedef
    clang::DeclContext *decl_context = inscls->getEnclosingNamespaceContext();
    for (auto decl : decl_context->decls()) {
      clang::TypedefDecl * tdef = clang::dyn_cast<clang::TypedefDecl>(decl);
      if (tdef && CLANG_GET_LOC_START(tdef) < CLANG_GET_LOC_START(inscls) &&
          CLANG_GET_LOC_END(tdef) >= CLANG_GET_LOC_END(inscls)) {
        inspos = CLANG_GET_LOC_START(tdef);
      }
    }

    _code_weaver.insert (_code_weaver.weave_pos (inspos, WeavePos::WP_BEFORE), inc, true);
  }
}

string substitute_names(const string &text, clang::TagDecl *target, int precedence,
    const std::vector<std::pair<size_t, TI_ClassSlice::SliceBody::InsertType> > &positions,
    bool &jp_needed) {
  string result;
  typedef TI_ClassSlice::SliceBody member_t;

  // generate names to be inserted at the appropriate positions
  std::string target_name = target->getNameAsString();
  std::string target_qual_name = target->getQualifiedNameAsString();
  std::string jp_name = "__TJP_" + llvm::itostr(precedence);

  jp_needed = false;
  size_t last_pos = 0;
  // The positions are sorted ascending.
  for (int pi = 0, pe = positions.size(); pi != pe; ++pi) {
    size_t pos = positions[pi].first;
    member_t::InsertType type = positions[pi].second;

    result += text.substr(last_pos, pos - last_pos);
    switch (type) {
    case member_t::TARGET_NAME:
      result += target_name;
      break;
    case member_t::TARGET_QUAL_NAME:
      result += target_qual_name;
      break;
    case member_t::JP_NAME:
      result += jp_name;
      jp_needed = true;
      break;
    }

    last_pos = pos;
  }
  result += text.substr (last_pos);

  return result;
}

void ClangIntroducer::gen_intros (ACM_Class *jpl,
                             std::deque<IntroductionUnit *> &units,
                             ACErrorStream &err,
                             clang::TagDecl *target, int introduction_depth,
  bool non_inline) const {
  typedef TI_ClassSlice::SliceBody member_t;

  // handle all intros
  typedef ACM_Container<ACM_MemberIntro, true> Container;
  Container &intros = jpl->get_plan()->get_member_intros();
  int i = 0;
  for (Container::iterator iter = intros.begin(); iter != intros.end(); ++iter, ++i) {
    ACM_Introduction *ii = (*iter)->get_intro();

    // TODO: clean up; a lot of duplicated code here
    TI_ClassSlice *ti = TI_ClassSlice::of (*get_slice (*ii));

    // generate non-inline introduction instance
    if (ti->non_inline_members ().size () > 0 && non_inline) {
      // Format the non-inline members. Everything is tokenized with the required
      // elements inserted in the given positions.
      std::vector<member_t> &bodies = ti->non_inline_members ();
      for (unsigned body_no = 0; body_no < bodies.size(); ++body_no) {
        const member_t &body = bodies[body_no];
        std::string str; // = "\n";

        // create the new unit
        IntroductionUnit *unit = new IntroductionUnit(body_no);
        unit->intro (ii);
        unit->precedence (i);

        bool jp_needed;
        str += substitute_names(body.text, target, i, body.positions, jp_needed);
        unit->jp_needed(jp_needed);

        // if there was no introduction, delete the unit -> no result
        if (str.empty() || str.find_first_not_of(" \n") == std::string::npos)
           delete unit;
         // check whether this is a deeply nested introduction
        else if (unit->nesting_level () > introduction_depth) {
          // FIXME: nesting level is always 0, because unit->target_unit is never called
          err << sev_error << target->getLocation()
              << "maximum level of nested introductions (" << introduction_depth
              << ") for class '"
              << target->getQualifiedNameAsString().c_str() << "' exceeded" << endMessage;
          err << sev_message
              << TI_Source::of(*(*ii).get_source().front())->start_loc()
              << "invalid introduction defined here" << endMessage;
          delete unit;
        }
        else {
          unit->content (str);
          units.push_back (unit);
        }
      }
      continue;
    }

    // create the new unit
    IntroductionUnit *unit = new IntroductionUnit(IntroductionUnit::IU_MEMBERS);
      //new IntroductionUnit (err, (Unit*)target->Tree ()->token ()->belonging_to ());
    unit->intro (ii);
    unit->precedence (i);
    std::string str; // = "\n";

    // generate inline introduction instance
    if (!ti->get_tokens().text.empty() && !non_inline) {
      // create a unit with the target class name
      std::string target_name = target->getNameAsString();

      std::string slice_start;
      if (get_slice(*ii)->get_is_struct())
        slice_start += "  public: ";
      else
        slice_start += "  private: ";
      // add "  typedef <target-name> <slice-name>;\n"
      if (get_slice(*ii)->get_name()[0] != '<') {
        slice_start += "typedef " + target_name + " "
                    + get_slice(*ii)->get_name() + "; ";
      }
      str += slice_start;

      const member_t &body = ti->get_tokens ();
      bool jp_needed;
      str += substitute_names(body.text, target, i, body.positions, jp_needed);
      unit->jp_needed(jp_needed);
    }

    // if there was no introduction, delete the unit -> no result
    if (str.empty() || str.find_first_not_of(" \n") == std::string::npos)
       delete unit;
     // check whether this is a deeply nested introduction
    else if (unit->nesting_level () > introduction_depth) {
      // FIXME: nesting level is always 0, because unit->target_unit is never called
      err << sev_error << target->getLocation()
          << "maximum level of nested introductions (" << introduction_depth
          << ") for class '"
          << target->getQualifiedNameAsString().c_str() << "' exceeded" << endMessage;
      err << sev_message
          << TI_Source::of(*(*ii).get_source().front())->start_loc()
          << "invalid introduction defined here" << endMessage;
      delete unit;
    }
    else {
      unit->content (str);
      units.push_back (unit);
    }
  }
}

void ClangIntroducer::gen_base_intros (ACM_Class *jpl,
                                  std::deque<IntroductionUnit *> &units,
                                  bool commas_only) const {
  typedef ACM_Container<ACM_BaseIntro, true> Container;
  Container &bases = jpl->get_plan()->get_base_intros();

  for (Container::iterator i = bases.begin(); i != bases.end (); ++i) {
    // get the current introduction
    ACM_Introduction *ii = (*i)->get_intro();

    // create the new unit
    IntroductionUnit *unit = new IntroductionUnit(IntroductionUnit::IU_BASE);
      //new IntroductionUnit (err, (Unit*)target->Tree ()->token ()->belonging_to ());
    unit->intro (ii);

    // generate the code for this base class introduction
    // FIXME: Line directives.
    std::string str;
    gen_base_intro (str, ii, (i == bases.begin()) && !commas_only);

    unit->content (str);

    // store the result for the caller
    units.push_back (unit);
  }
}

void ClangIntroducer::gen_base_intro (std::string &unit,
  ACM_Introduction *ii, bool first) const {
  TI_ClassSlice *ti = TI_ClassSlice::of (*get_slice(*ii));
  // generate the code for this entry
  unit += (first ? ": " : ", ");
  unit += ti->base_intro();
}
