// @(#)root/html:$Id$
// Author: Axel Naumann 2007-01-09
/*************************************************************************
* Copyright (C) 1995-2007, Rene Brun and Fons Rademakers. *
* All rights reserved. *
* *
* For the licensing terms see $ROOTSYS/LICENSE. *
* For the list of contributors see $ROOTSYS/README/CREDITS. *
*************************************************************************/
#include "TDocParser.h"
#include "Riostream.h"
#include "TBaseClass.h"
#include "TClass.h"
#include "TClassDocOutput.h"
#include "TDataMember.h"
#include "TDataType.h"
#include "TDatime.h"
#include "TDocDirective.h"
#include "TEnv.h"
#include "TGlobal.h"
#include "THtml.h"
#include "TInterpreter.h"
#include "TMethod.h"
#include "TMethodArg.h"
#include "TPRegexp.h"
#include "TROOT.h"
#include "TSystem.h"
#include "TVirtualMutex.h"
#include
namespace {
class TMethodWrapperImpl: public TDocMethodWrapper {
public:
TMethodWrapperImpl(TMethod* m, int overloadIdx):
fMeth(m), fOverloadIdx(overloadIdx) {}
static void SetClass(const TClass* cl) { fgClass = cl; }
const char* GetName() const { return fMeth->GetName(); }
ULong_t Hash() const { return fMeth->Hash();}
Int_t GetNargs() const { return fMeth->GetNargs(); }
virtual TMethod* GetMethod() const { return fMeth; }
Bool_t IsSortable() const { return kTRUE; }
Int_t GetOverloadIdx() const { return fOverloadIdx; }
Int_t Compare(const TObject *obj) const {
const TMethodWrapperImpl* m = dynamic_cast(obj);
if (!m) return 1;
Int_t ret = strcasecmp(GetName(), m->GetName());
if (ret == 0) {
if (GetNargs() < m->GetNargs()) return -1;
else if (GetNargs() > m->GetNargs()) return 1;
if (GetMethod()->GetClass()->InheritsFrom(m->GetMethod()->GetClass()))
return -1;
else
return 1;
}
const char* l(GetName());
const char* r(m->GetName());
if (l[0] == '~' && r[0] == '~') {
++l;
++r;
}
if (fgClass->InheritsFrom(l)) {
if (fgClass->InheritsFrom(r)) {
if (gROOT->GetClass(l)->InheritsFrom(r))
return -1;
else return 1;
} else return -1;
} else if (fgClass->InheritsFrom(r))
return 1;
if (l[0] == '~') return -1;
if (r[0] == '~') return 1;
return (ret < 0) ? -1 : 1;
}
private:
static const TClass* fgClass; // current class, defining inheritance sort order
TMethod* fMeth; // my method
Int_t fOverloadIdx; // this is the n-th overload
};
const TClass* TMethodWrapperImpl::fgClass = 0;
}
//______________________________________________________________________________
////////////////////////////////////////////////////////////////////////////////
//
// Parse C++ source or header, and extract documentation.
//
// Also handles special macros like
/* Begin_Macro(GUI, source)
{
TGMainFrame* f = new TGMainFrame(0, 100, 100);
f->SetName("testMainFrame"); // that's part of the name of the image
TGButton* b = new TGTextButton(f, "Test Button");
f->AddFrame(b);
f->MapSubwindows();
f->Resize(f->GetDefaultSize());
f->MapWindow();
return f; // *HIDE*
}
End_Macro */
// or multiline Latex aligned at =:
/* Begin_Latex(separator='=',align=rcl) C = d #sqrt{#frac{2}{#lambdaD}} #int^{x}_{0}cos(#frac{#pi}{2}t^{2})dt
D(x) = d End_Latex */
// even without alignment: Begin_Latex
// x=sin^2(y)
// y = #sqrt{sin(x)}
// End_Latex and what about running an external macro?
/* BEGIN_MACRO(source)
testmacro.C END_MACRO
and some nested stuff which doesn't work yet: */
// BEGIN_HTML
/* BEGIN_LATEX Wow,^{an}_{image}^{inside}_{a}^{html}_{block}
END_LATEX
*/
// END_HTML
////////////////////////////////////////////////////////////////////////////////
ClassImp(TDocParser);
std::set TDocParser::fgKeywords;
//______________________________________________________________________________
TDocParser::TDocParser(TClassDocOutput& docOutput, TClass* cl):
fHtml(docOutput.GetHtml()), fDocOutput(&docOutput), fLineNo(0),
fCurrentClass(cl), fRecentClass(0), fCurrentModule(0),
fDirectiveCount(0), fLineNumber(0), fDocContext(kIgnore),
fCheckForMethod(kFALSE), fClassDocState(kClassDoc_Uninitialized),
fCommentAtBOL(kFALSE), fAllowDirectives(kTRUE)
{
// Constructor called for parsing class sources
InitKeywords();
fSourceInfoTags[kInfoLastUpdate] = fHtml->GetLastUpdateTag();
fSourceInfoTags[kInfoAuthor] = fHtml->GetAuthorTag();
fSourceInfoTags[kInfoCopyright] = fHtml->GetCopyrightTag();
fClassDescrTag = fHtml->GetClassDocTag();
TMethodWrapperImpl::SetClass(cl);
for (int ia = 0; ia < 3; ++ia) {
fMethods[ia].Rehash(101);
}
AddClassMethodsRecursively(0);
AddClassDataMembersRecursively(0);
// needed for list of methods,...
fParseContext.push_back(kCode);
// create an array of method names
TMethod *method;
TIter nextMethod(fCurrentClass->GetListOfMethods());
fMethodCounts.clear();
while ((method = (TMethod *) nextMethod())) {
++fMethodCounts[method->GetName()];
}
}
//______________________________________________________________________________
TDocParser::TDocParser(TDocOutput& docOutput):
fHtml(docOutput.GetHtml()), fDocOutput(&docOutput), fLineNo(0),
fCurrentClass(0), fRecentClass(0), fDirectiveCount(0),
fLineNumber(0), fDocContext(kIgnore),
fCheckForMethod(kFALSE), fClassDocState(kClassDoc_Uninitialized),
fCommentAtBOL(kFALSE), fAllowDirectives(kFALSE)
{
// constructor called for parsing text files with Convert()
InitKeywords();
fSourceInfoTags[kInfoLastUpdate] = fHtml->GetLastUpdateTag();
fSourceInfoTags[kInfoAuthor] = fHtml->GetAuthorTag();
fSourceInfoTags[kInfoCopyright] = fHtml->GetCopyrightTag();
fClassDescrTag = fHtml->GetClassDocTag();
TMethodWrapperImpl::SetClass(0);
}
//______________________________________________________________________________
TDocParser::~TDocParser()
{
// destructor, checking whether all methods have been found for gDebug > 3
if (gDebug > 3) {
for (std::map::const_iterator iMethod = fMethodCounts.begin();
iMethod != fMethodCounts.end(); ++iMethod)
if (iMethod->second)
Info("~TDocParser", "Implementation of method %s::%s could not be found.",
fCurrentClass ? fCurrentClass->GetName() : "",
iMethod->first.c_str());
TIter iDirective(&fDirectiveHandlers);
TDocDirective* directive = 0;
while ((directive = (TDocDirective*) iDirective())) {
TString directiveName;
directive->GetName(directiveName);
Warning("~TDocParser", "Missing \"%s\" for macro %s", directive->GetEndTag(), directiveName.Data());
}
}
}
//______________________________________________________________________________
void TDocParser::AddClassMethodsRecursively(TBaseClass* bc)
{
// Add accessible (i.e. non-private) methods of base class bc
// and its base classes' methods to methodNames.
// If bc==0, we add fCurrentClass's methods (and also private functions).
// make a loop on member functions
TClass *cl = fCurrentClass;
if (bc)
cl = bc->GetClassPointer(kFALSE);
if (!cl) return;
TMethod *method;
TIter nextMethod(cl->GetListOfMethods());
std::map methOverloads;
while ((method = (TMethod *) nextMethod())) {
if (!strcmp(method->GetName(), "Dictionary") ||
!strcmp(method->GetName(), "Class_Version") ||
!strcmp(method->GetName(), "Class_Name") ||
!strcmp(method->GetName(), "DeclFileName") ||
!strcmp(method->GetName(), "DeclFileLine") ||
!strcmp(method->GetName(), "ImplFileName") ||
!strcmp(method->GetName(), "ImplFileLine") ||
(bc && (method->GetName()[0] == '~' // d'tor
|| !strcmp(method->GetName(), method->GetReturnTypeName()))) // c'tor
)
continue;
Int_t mtype = 0;
if (kIsPrivate & method->Property())
mtype = 0;
else if (kIsProtected & method->Property())
mtype = 1;
else if (kIsPublic & method->Property())
mtype = 2;
if (bc) {
if (mtype == 0) continue;
if (bc->Property() & kIsPrivate)
mtype = 0;
else if ((bc->Property() & kIsProtected) && mtype == 2)
mtype = 1;
}
Bool_t hidden = kFALSE;
for (Int_t access = 0; !hidden && access < 3; ++access) {
TMethodWrapperImpl* other = (TMethodWrapperImpl*) fMethods[access].FindObject(method->GetName());
hidden |= (other) && (other->GetMethod()->GetClass() != method->GetClass());
}
if (!hidden) {
fMethods[mtype].Add(new TMethodWrapperImpl(method, methOverloads[method->GetName()]));
++methOverloads[method->GetName()];
}
}
TIter iBase(cl->GetListOfBases());
TBaseClass* base = 0;
while ((base = (TBaseClass*)iBase()))
AddClassMethodsRecursively(base);
if (!bc)
for (Int_t access = 0; access < 3; ++access) {
fMethods[access].SetOwner();
fMethods[access].Sort();
}
}
//______________________________________________________________________________
void TDocParser::AddClassDataMembersRecursively(TBaseClass* bc) {
// Add data members of fCurrentClass and of bc to datamembers, recursively.
// Real data members are in idx 0..2 (public, protected, private access),
// enum constants in idx 3..5.
// make a loop on member functions
TClass *cl = fCurrentClass;
if (bc)
cl = bc->GetClassPointer(kFALSE);
if (!cl) return;
TDataMember *dm;
TIter nextDM(cl->GetListOfDataMembers());
while ((dm = (TDataMember *) nextDM())) {
if (!strcmp(dm->GetName(), "fgIsA"))
continue;
Int_t mtype = 0;
if (kIsPrivate & dm->Property())
mtype = 0;
else if (kIsProtected & dm->Property())
mtype = 1;
else if (kIsPublic & dm->Property())
mtype = 2;
if (bc) {
if (mtype == 0) continue;
if (bc->Property() & kIsPrivate)
mtype = 0;
else if ((bc->Property() & kIsProtected) && mtype == 2)
mtype = 1;
}
const Int_t flagEnumConst = G__BIT_ISENUM | G__BIT_ISCONSTANT | G__BIT_ISSTATIC;
if ((dm->Property() & flagEnumConst) == flagEnumConst
&& dm->GetDataType() && dm->GetDataType()->GetType() == kInt_t) {
mtype = 5;
// The access of the enum constant is defined by the access of the enum:
// for CINT, all enum constants are public.
// There is no TClass or TDataType for enum types; instead, use CINT:
/*
No - CINT does not know their access restriction.
With CINT5 we have no way of determining it...
ClassInfo_t* enumCI = gInterpreter->ClassInfo_Factory(dm->GetTypeName());
if (enumCI) {
Long_t prop = gInterpreter->ClassInfo_Property(enumCI);
if (kIsPrivate & prop)
mtype = 3;
else if (kIsProtected & prop)
mtype = 4;
else if (kIsPublic & prop)
mtype = 5;
gInterpreter->ClassInfo_Delete(enumCI);
}
*/
}
fDataMembers[mtype].Add(dm);
}
TIter iBase(cl->GetListOfBases());
TBaseClass* base = 0;
while ((base = (TBaseClass*)iBase()))
AddClassDataMembersRecursively(base);
if (!bc)
for (Int_t access = 0; access < 6; ++access) {
fDataMembers[access].SetOwner(kFALSE);
if (access < 3) // don't sort enums; we keep them in enum tag order
fDataMembers[access].Sort();
}
}
//______________________________________________________________________________
void TDocParser::AnchorFromLine(const TString& line, TString& anchor) {
// Create an anchor from the given line, by hashing it and
// convertig the hash into a custom base64 string.
const char base64String[65] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789_.";
// use hash of line instead of e.g. line number.
// advantages: more stable (lines can move around, we still find them back),
// no need for keeping a line number context
UInt_t hash = ::Hash(line);
anchor.Remove(0);
// force first letter to be [A-Za-z], to be id compatible
anchor += base64String[hash % 52];
hash /= 52;
while (hash) {
anchor += base64String[hash % 64];
hash /= 64;
}
}
//______________________________________________________________________________
void TDocParser::Convert(std::ostream& out, std::istream& in, const char* relpath,
Bool_t isCode, Bool_t interpretDirectives)
{
// Parse text file "in", add links etc, and write output to "out".
// If "isCode", "in" is assumed to be C++ code.
fLineNumber = 0;
fParseContext.clear();
if (isCode) fParseContext.push_back(kCode);
else fParseContext.push_back(kComment); // so we can find "BEGIN_HTML"/"END_HTML" in plain text
while (!in.eof()) {
fLineRaw.ReadLine(in, kFALSE);
++fLineNumber;
if (in.eof())
break;
// remove leading spaces
fLineComment = "";
fLineSource = fLineRaw;
fLineStripped = fLineRaw;
Strip(fLineStripped);
DecorateKeywords(fLineSource);
ProcessComment();
// Changes in this bit of code have consequences for:
// * module index,
// * source files,
// * THtml::Convert() e.g. in tutorials/html/MakeTutorials.C
if (!interpretDirectives) {
// Only write the raw, uninterpreted directive code:
if (!InContext(kDirective)) {
GetDocOutput()->AdjustSourcePath(fLineSource, relpath);
out << fLineSource << endl;
}
} else {
// Write source for source and interpreted directives if they exist.
if (fLineComment.Length() ) {
GetDocOutput()->AdjustSourcePath(fLineComment, relpath);
out << fLineComment << endl;
} else if (!InContext(kDirective)) {
GetDocOutput()->AdjustSourcePath(fLineSource, relpath);
out << fLineSource << endl;
}
}
}
}
//______________________________________________________________________________
void TDocParser::DecorateKeywords(std::ostream& out, const char *text)
{
// Expand keywords in text, writing to out.
TString str(text);
DecorateKeywords(str);
out << str;
}
//______________________________________________________________________________
void TDocParser::DecorateKeywords(TString& line)
{
// Find keywords in line and create URLs around them. Escape characters with a
// special meaning for HTML. Protect "Begin_Html"/"End_Html" pairs, and set the
// parsing context. Evaluate sequences like a::b->c.
// Skip regions where directives are active.
std::list currentType;
enum {
kNada,
kMember,
kScope,
kNumAccesses
} scoping = kNada;
currentType.push_back(0);
Ssiz_t i = 0;
while (isspace((UChar_t)line[i]))
++i;
Ssiz_t startOfLine = i;
// changed when the end of a directive is encountered, i.e.
// from where fLineSource needs to be appended to fLineComment
Ssiz_t copiedToCommentUpTo = 0;
if (InContext(kDirective) && fDirectiveHandlers.Last()) {
// we're only waiting for an "End_Whatever" and ignoring everything else
TDocDirective* directive = (TDocDirective*)fDirectiveHandlers.Last();
const char* endTag = directive->GetEndTag();
Ssiz_t posEndTag = i;
while (kNPOS != (posEndTag = line.Index(endTag, posEndTag, TString::kIgnoreCase)))
if (posEndTag == 0 || line[posEndTag - 1] != '"') // escaping '"'
break;
if (posEndTag != kNPOS)
i = posEndTag;
else {
Ssiz_t start = 0;
if (!InContext(kComment) || (InContext(kComment) & kCXXComment)) {
// means we are in a C++ comment
while (isspace((UChar_t)fLineRaw[start])) ++start;
if (fLineRaw[start] == '/' && fLineRaw[start + 1] == '/')
start += 2;
else start = 0;
}
directive->AddLine(fLineRaw(start, fLineRaw.Length()));
while(i < line.Length())
fDocOutput->ReplaceSpecialChars(line, i);
copiedToCommentUpTo = i;
}
}
for (; i < line.Length(); ++i) {
if (!currentType.back())
scoping = kNada;
// evaluate scope relation
if (Context() == kCode
|| Context() == kComment) {
if (currentType.back())
switch (line[i]) {
case ':':
if (line[i + 1] == ':') {
scoping = kScope;
i += 1;
continue;
}
break;
case '-':
if (line[i + 1] == '>') {
scoping = kMember;
i += 1;
continue;
}
break;
case '.':
if (line[i + 1] != '.') {
// prevent "..."
scoping = kMember;
continue;
}
break;
}
switch (line[i]) {
case '(':
currentType.push_back(0);
scoping = kNada;
continue;
break;
case ')':
if (currentType.size() > 1)
currentType.pop_back();
scoping = kMember;
continue;
break;
}
if (i >= line.Length())
break;
} else // code or comment
currentType.back() = 0;
if (!IsWord(line[i])){
Bool_t haveHtmlEscapedChar = Context() == kString
&& i > 2 && line[i] == '\'' && line[i-1] == ';';
if (haveHtmlEscapedChar) {
Ssiz_t posBegin = i - 2;
while (posBegin > 0 && IsWord(line[posBegin]))
--posBegin;
haveHtmlEscapedChar = posBegin > 0 &&
line[posBegin] == '&' && line[posBegin - 1] == '\'';
}
EParseContext context = Context();
Bool_t closeString = context == kString
&& ( line[i] == '"'
|| (line[i] == '\''
&& ( (i > 1 && line[i - 2] == '\'')
|| (i > 3 && line[i - 2] == '\\' && line[i - 3] == '\'')))
|| haveHtmlEscapedChar)
&& (i == 0 || line[i - 1] != '\\'); // but not "foo \"str...
if (context == kCode || context == kComment) {
if (line[i] == '"' || (line[i] == '\'' && (
// 'a'
(line.Length() > i + 2 && line[i + 2] == '\'') ||
// '\a'
(line.Length() > i + 3 && line[i + 1] == '\'' && line[i + 3] == '\'')))) {
fDocOutput->DecorateEntityBegin(line, i, kString);
fParseContext.push_back(kString);
currentType.back() = 0;
closeString = kFALSE;
} else if (context == kCode
&& line[i] == '/' && (line[i+1] == '/' || line[i+1] == '*')) {
fParseContext.push_back(kComment);
if (line[i+1] == '/')
fParseContext.back() |= kCXXComment;
currentType.back() = 0;
fDocOutput->DecorateEntityBegin(line, i, kComment);
++i;
} else if (context == kComment
&& !(fParseContext.back() & kCXXComment)
&& line.Length() > i + 1
&& line[i] == '*' && line[i+1] == '/') {
if (fParseContext.size()>1)
fParseContext.pop_back();
currentType.back() = 0;
i += 2;
fDocOutput->DecorateEntityEnd(line, i, kComment);
if (!fCommentAtBOL) {
if (InContext(kDirective))
((TDocDirective*)fDirectiveHandlers.Last())->AddLine(line(copiedToCommentUpTo, i));
else
fLineComment += line(copiedToCommentUpTo, i);
copiedToCommentUpTo = i;
}
} else if (startOfLine == i
&& line[i] == '#'
&& context == kCode) {
ExpandCPPLine(line, i);
}
} // if context is comment or code
if (i < line.Length())
fDocOutput->ReplaceSpecialChars(line, i);
if (closeString) {
fDocOutput->DecorateEntityEnd(line, i, kString);
if (fParseContext.size()>1)
fParseContext.pop_back();
currentType.back() = 0;
}
--i; // i already moved by ReplaceSpecialChar
continue;
} // end of "not a word"
// get the word
Ssiz_t endWord = i;
while (endWord < line.Length() && IsName(line[endWord]))
endWord++;
if (Context() == kString || Context() == kCPP) {
// don't replace in strings, cpp, etc
i = endWord - 1;
continue;
}
TString word(line(i, endWord - i));
// '"' escapes handling of "Begin_..."/"End_..."
if ((i == 0 || (i > 0 && line[i - 1] != '"'))
&& HandleDirective(line, i, word, copiedToCommentUpTo)) {
// something special happened; the currentType is gone.
currentType.back() = 0;
continue;
}
// don't replace keywords in comments
if (Context() == kCode
&& fgKeywords.find(word.Data()) != fgKeywords.end()) {
fDocOutput->DecorateEntityBegin(line, i, kKeyword);
i += word.Length();
fDocOutput->DecorateEntityEnd(line, i, kKeyword);
--i; // -1 for ++i
currentType.back() = 0;
continue;
}
// Now decorate scopes and member, referencing their documentation:
// generic layout:
// A::B::C::member[arr]->othermember
// we iterate through this, first scope is A, and currentType will be set toA,
// next we see ::B, "::" signals to use currentType,...
TDataType* subType = 0;
TClass* subClass = 0;
TDataMember *datamem = 0;
TMethod *meth = 0;
const char* globalTypeName = 0;
if (!currentType.size()) {
Warning("DecorateKeywords", "type context is empty!");
currentType.push_back(0);
}
TClass* lookupScope = currentType.back();
if (scoping == kNada) {
if (fCurrentClass)
lookupScope = fCurrentClass;
else
lookupScope = fRecentClass;
}
if (scoping == kNada) {
subType = gROOT->GetType(word);
if (!subType)
subClass = fHtml->GetClass(word);
if (!subType && !subClass) {
TGlobal *global = gROOT->GetGlobal(word);
if (global) {
// cannot doc globals; take at least their type...
globalTypeName = global->GetTypeName();
subClass = fHtml->GetClass(globalTypeName);
if (!subClass)
subType = gROOT->GetType(globalTypeName);
else // hack to prevent current THtml obj from showing up - we only want gHtml
if (subClass == THtml::Class() && word != "gHtml")
subClass = 0;
}
}
if (!subType && !subClass) {
// too bad - cannot doc yet...
//TFunction *globFunc = gROOT->GetGlobalFunctionWithPrototype(word);
//globFunc = 0;
}
if (!subType && !subClass) {
// also try template
while (isspace(line[endWord])) ++endWord;
if (line[endWord] == '<' || line[endWord] == '>') {
// check for possible template
Ssiz_t endWordT = endWord + 1;
int templateLevel = 1;
while (endWordT < line.Length()
&& (templateLevel
|| IsName(line[endWordT])
|| line[endWordT] == '<'
|| line[endWordT] == '>')) {
if (line[endWordT] == '<')
++templateLevel;
else if (line[endWordT] == '>')
--templateLevel;
endWordT++;
}
subClass = fHtml->GetClass(line(i, endWordT - i).Data());
if (subClass)
word = line(i, endWordT - i);
}
}
}
if (lookupScope && !subType && !subClass) {
if (scoping == kScope) {
TString subClassName(lookupScope->GetName());
subClassName += "::";
subClassName += word;
subClass = fHtml->GetClass(subClassName);
if (!subClass)
subType = gROOT->GetType(subClassName);
}
if (!subClass && !subType) {
// also try A::B::c()
datamem = lookupScope->GetDataMember(word);
if (!datamem)
meth = lookupScope->GetMethodAllAny(word);
}
if (!subClass && !subType && !datamem && !meth) {
// also try template
while (isspace(line[endWord])) ++endWord;
if (line[endWord] == '<' || line[endWord] == '>') {
// check for possible template
Ssiz_t endWordT = endWord + 1;
int templateLevel = 1;
while (endWordT < line.Length()
&& (templateLevel
|| IsName(line[endWordT])
|| line[endWordT] == '<'
|| line[endWordT] == '>')) {
if (line[endWordT] == '<')
++templateLevel;
else if (line[endWordT] == '>')
--templateLevel;
endWordT++;
}
TString subClassName(lookupScope->GetName());
subClassName += "::";
subClassName += line(i, endWordT - i);
subClass = fHtml->GetClass(subClassName);
if (subClass)
word = line(i, endWordT - i);
}
}
}
// create the link
TString mangledWord(word);
fDocOutput->ReplaceSpecialChars(mangledWord);
line.Replace(i, word.Length(), mangledWord);
TSubString substr(line(i, mangledWord.Length()));
if (subType) {
fDocOutput->ReferenceEntity(substr, subType,
globalTypeName ? globalTypeName : subType->GetName());
currentType.back() = 0;
} else if (subClass) {
fDocOutput->ReferenceEntity(substr, subClass,
globalTypeName ? globalTypeName : subClass->GetName());
currentType.back() = subClass;
fRecentClass = subClass;
} else if (datamem || meth) {
if (datamem) {
fDocOutput->ReferenceEntity(substr, datamem);
if (datamem->GetTypeName())
currentType.back() = fHtml->GetClass(datamem->GetTypeName());
} else {
fDocOutput->ReferenceEntity(substr, meth);
TString retTypeName = meth->GetReturnTypeName();
if (retTypeName.BeginsWith("const "))
retTypeName.Remove(0,6);
Ssiz_t pos=0;
while (IsWord(retTypeName[pos]) || retTypeName[pos]=='<' || retTypeName[pos]=='>' || retTypeName[pos]==':')
++pos;
retTypeName.Remove(pos, retTypeName.Length());
if (retTypeName.Length())
currentType.back() = fHtml->GetClass(retTypeName);
}
} else
currentType.back() = 0;
//i += mangledWord.Length();
i += substr.Length();
--i; // due to ++i
} // while i < line.Length()
if (i > line.Length())
i = line.Length();
// clean up, no strings across lines
if (Context() == kString) {
fDocOutput->DecorateEntityEnd(line, i, kString);
if (fParseContext.size()>1)
fParseContext.pop_back();
currentType.back() = 0;
}
// HandleDirective already copied the chunk before the directive
// from fLineSource to fLineComment. So we're done up to "i" in
// fLineSource; next time we encounter a directive we just need
// to copy from startOfComment on.
if ((InContext(kComment) || fCommentAtBOL) && copiedToCommentUpTo < line.Length()) {
if (InContext(kDirective))
((TDocDirective*)fDirectiveHandlers.Last())->AddLine(line(copiedToCommentUpTo, line.Length()));
else
fLineComment += line(copiedToCommentUpTo, line.Length());
}
// Do this after we append to fLineComment, otherwise the closing
// gets sent to the directive.
// clean up, no CPP comment across lines
if (InContext(kComment) & kCXXComment) {
fDocOutput->DecorateEntityEnd(line, i, kComment);
if (fLineComment.Length()) {
Ssiz_t pos = fLineComment.Length();
fDocOutput->DecorateEntityEnd(fLineComment, pos, kComment);
}
RemoveCommentContext(kTRUE);
currentType.back() = 0;
}
}
//______________________________________________________________________________
void TDocParser::DecrementMethodCount(const char* name)
{
// reduce method count for method called name,
// removing it from fMethodCounts once the count reaches 0.
typedef std::map MethodCount_t;
MethodCount_t::iterator iMethodName = fMethodCounts.find(name);
if (iMethodName != fMethodCounts.end()) {
--(iMethodName->second);
if (iMethodName->second <= 0)
fMethodCounts.erase(iMethodName);
}
}
//______________________________________________________________________________
void TDocParser::DeleteDirectiveOutput() const
{
// Delete output generated by prior runs of all known directives;
// the output file names might have changes.
TIter iClass(gROOT->GetListOfClasses());
TClass* cl = 0;
while ((cl = (TClass*) iClass()))
if (cl != TDocDirective::Class()
&& cl->InheritsFrom(TDocDirective::Class())) {
TDocDirective* directive = (TDocDirective*) cl->New();
if (!directive) continue;
directive->SetParser(const_cast(this));
directive->DeleteOutput();
delete directive;
}
}
//______________________________________________________________________________
void TDocParser::ExpandCPPLine(TString& line, Ssiz_t& pos)
{
// Expand preprocessor statements
//
//
// Input: line - line containing the CPP statement,
// pos - position of '#'
//
// NOTE: Looks for the #include statements and
// creates link to the corresponding file
// if such file exists
//
Bool_t linkExist = kFALSE;
Ssiz_t posEndOfLine = line.Length();
Ssiz_t posHash = pos;
Ssiz_t posInclude = line.Index("include", pos);
if (posInclude != kNPOS) {
TString filename;
Ssiz_t posStartFilename = posInclude + 7;
if (line.Tokenize(filename, posStartFilename, "[<\"]")) {
Ssiz_t posEndFilename = posStartFilename;
if (line.Tokenize(filename, posEndFilename, "[>\"]")) {
R__LOCKGUARD(fHtml->GetMakeClassMutex());
TString filesysFileName;
if (fHtml->GetPathDefinition().GetFileNameFromInclude(filename, filesysFileName)) {
fDocOutput->CopyHtmlFile(filesysFileName);
TString endOfLine(line(posEndFilename - 1, line.Length()));
line.Remove(posStartFilename, line.Length());
for (Ssiz_t i = pos; i < line.Length();)
fDocOutput->ReplaceSpecialChars(line, i);
line += "BaseName(filename);
line += "\">";
line += filename + "" + endOfLine[0]; // add include file's closing '>' or '"'
posEndOfLine = line.Length() - 1; // set the "processed up to" to it
fDocOutput->ReplaceSpecialChars(line, posEndOfLine); // and run replace-special-char on it
line += endOfLine(1, endOfLine.Length()); // add the unprocessed part of the line back
linkExist = kTRUE;
}
}
}
}
if (!linkExist) {
fDocOutput->ReplaceSpecialChars(line);
posEndOfLine = line.Length();
}
Ssiz_t posHashAfterDecoration = posHash;
fDocOutput->DecorateEntityBegin(line, posHashAfterDecoration, kCPP);
posEndOfLine += posHashAfterDecoration - posHash;
fDocOutput->DecorateEntityEnd(line, posEndOfLine, kCPP);
pos = posEndOfLine;
}
//______________________________________________________________________________
void TDocParser::GetCurrentModule(TString& out_module) const {
// Return the name of module for which sources are currently parsed.
if (fCurrentModule) out_module = fCurrentModule;
else if (fCurrentClass) fHtml->GetModuleNameForClass(out_module, fCurrentClass);
else out_module = "(UNKNOWN MODULE WHILE PARSING)";
}
//______________________________________________________________________________
Bool_t TDocParser::HandleDirective(TString& line, Ssiz_t& pos, TString& word,
Ssiz_t& copiedToCommentUpTo)
{
// Process directives to the documentation engine, like "Begin_Html" / "End_Html",
// "Begin_Macro" / "End_Macro", and "Begin_Latex" / "End_Latex".
Bool_t begin = kTRUE;
TClass* clDirective = IsDirective(line, pos, word, begin);
if (!clDirective)
return kFALSE;
// we'll need end later on: afer the begin block, both end _and_ begin can be true.
Bool_t end = !begin;
TDocDirective* directive = 0; // allow re-use of object from begin block in end
if (begin) {
// copy from fLineSource to fLineComment, starting at copiedToCommentUpTo
if (InContext(kDirective))
((TDocDirective*)fDirectiveHandlers.Last())->AddLine(fLineSource(copiedToCommentUpTo, pos - copiedToCommentUpTo));
else
fLineComment += fLineSource(copiedToCommentUpTo, pos - copiedToCommentUpTo);
copiedToCommentUpTo = pos;
pos += word.Length(); // skip the keyword
directive = (TDocDirective*) clDirective->New();
if (!directive)
return kFALSE;
directive->SetParser(this);
if (fCurrentMethodTag.Length())
directive->SetTag(fCurrentMethodTag);
directive->SetCounter(fDirectiveCount++);
// parse parameters
TString params;
if (begin && line[pos] == '(') {
std::list waitForClosing;
Ssiz_t endParam = pos + 1;
for (; endParam < line.Length()
&& (line[endParam] != ')' || !waitForClosing.empty()); ++endParam) {
const char c = line[endParam];
if (!waitForClosing.empty() && waitForClosing.back() == c) {
waitForClosing.pop_back();
continue;
}
switch (c) {
case '"':
if (waitForClosing.empty() || waitForClosing.back() != '\'')
waitForClosing.push_back('"');
break;
case '\'':
if (waitForClosing.empty() || waitForClosing.back() != '"')
waitForClosing.push_back('\'');
break;
case '(':
if (waitForClosing.empty() || (waitForClosing.back() != '"' && waitForClosing.back() != '\''))
waitForClosing.push_back(')');
break;
case '\\':
++endParam; // skip next char
default:
break;
};
}
if (waitForClosing.empty()) {
params = line(pos + 1, endParam - (pos + 1));
pos += params.Length() + 2; // params + parentheses
}
directive->SetParameters(params);
}
// check for end tag in current line
Ssiz_t posEndTag = pos;
const char* endTag = directive->GetEndTag();
Ssiz_t lenEndTag = strlen(endTag);
while (kNPOS != (posEndTag = line.Index(endTag, posEndTag, TString::kIgnoreCase))) {
if (line[posEndTag - 1] == '"') {
posEndTag += lenEndTag;
continue; // escaping '"'
}
break;
}
if (posEndTag != kNPOS) {
end = kTRUE; // we just continue below!
} else {
fDirectiveHandlers.AddLast(directive);
fParseContext.push_back(kDirective);
if (InContext(kComment) & kCXXComment)
fParseContext.back() |= kCXXComment;
posEndTag = line.Length();
}
directive->AddLine(line(pos, posEndTag - pos));
TString remainder(line(posEndTag, line.Length()));
line.Remove(posEndTag, line.Length());
while (pos < line.Length())
fDocOutput->ReplaceSpecialChars(line, pos);
pos = line.Length();
// skip the remainder of the line
copiedToCommentUpTo = line.Length();
line += remainder;
}
// no else - "end" can also be set by begin having an end tag!
if (end) {
if (!begin)
pos += word.Length(); // skip the keyword
else pos += word.Length() - 2; // "Begin" is 2 chars longer than "End"
if (!directive) directive = (TDocDirective*) fDirectiveHandlers.Last();
if (!directive) {
Warning("HandleDirective", "Cannot find directive handler object %s !",
fLineRaw.Data());
return kFALSE;
}
if (!begin) {
Ssiz_t start = 0;
if (!InContext(kComment) || (InContext(kComment) & kCXXComment)) {
// means we are in a C++ comment
while (isspace((UChar_t)fLineRaw[start])) ++start;
if (fLineRaw[start] == '/' && fLineRaw[start + 1] == '/')
start += 2;
else start = 0;
}
directive->AddLine(line(start, pos - word.Length() - start));
TString remainder(line(pos, line.Length()));
line.Remove(pos, line.Length());
fDocOutput->ReplaceSpecialChars(line);
pos = line.Length();
line += remainder;
}
copiedToCommentUpTo = pos;
TString result;
directive->GetResult(result);
if (!begin)
fDirectiveHandlers.Remove(fDirectiveHandlers.LastLink());
delete directive;
if (!begin) {
// common to all directives: pop context
Bool_t isInCxxComment = InContext(kDirective) & kCXXComment;
if (fParseContext.size()>1)
fParseContext.pop_back();
if (isInCxxComment && !InContext(kComment)) {
fParseContext.push_back(kComment | kCXXComment);
fDocOutput->DecorateEntityBegin(line, pos, kComment);
}
}
if (InContext(kDirective) && fDirectiveHandlers.Last())
((TDocDirective*)fDirectiveHandlers.Last())->AddLine(result(0, result.Length()));
else
fLineComment += result;
/* NO - this can happen e.g. for "BEGIN_HTML / *..." (see doc in this class)
if (Context() != kComment) {
Warning("HandleDirective", "Popping back a directive context, but enclosing context is not a comment! At:\n%s",
fLineRaw.Data());
fParseContext.push_back(kComment);
}
*/
}
return kTRUE;
}
//______________________________________________________________________________
UInt_t TDocParser::InContext(Int_t context) const
{
// checks whether we are in a parse context, return the entry closest
// to the current context.
// If context is a EParseContextFlag just look for the first match in
// the flags
UInt_t lowerContext = context & kParseContextMask;
UInt_t contextFlag = context & kParseContextFlagMask;
for (std::list::const_reverse_iterator iPC = fParseContext.rbegin();
iPC != fParseContext.rend(); ++iPC)
if (!lowerContext || ((lowerContext && ((*iPC & kParseContextMask) == lowerContext))
&& (!contextFlag || (contextFlag && (*iPC & contextFlag)))))
return *iPC;
return 0;
}
//______________________________________________________________________________
void TDocParser::InitKeywords() const
{
// fill C++ keywords into fgKeywords
if (!fgKeywords.empty())
return;
fgKeywords.insert("asm");
fgKeywords.insert("auto");
fgKeywords.insert("bool");
fgKeywords.insert("break");
fgKeywords.insert("case");
fgKeywords.insert("catch");
fgKeywords.insert("char");
fgKeywords.insert("class");
fgKeywords.insert("const");
fgKeywords.insert("const_cast");
fgKeywords.insert("continue");
fgKeywords.insert("default");
fgKeywords.insert("delete");
fgKeywords.insert("do");
fgKeywords.insert("double");
fgKeywords.insert("dynamic_cast");
fgKeywords.insert("else");
fgKeywords.insert("enum");
fgKeywords.insert("explicit");
fgKeywords.insert("export");
fgKeywords.insert("extern");
fgKeywords.insert("false");
fgKeywords.insert("float");
fgKeywords.insert("for");
fgKeywords.insert("friend");
fgKeywords.insert("goto");
fgKeywords.insert("if");
fgKeywords.insert("inline");
fgKeywords.insert("int");
fgKeywords.insert("long");
fgKeywords.insert("mutable");
fgKeywords.insert("namespace");
fgKeywords.insert("new");
fgKeywords.insert("operator");
fgKeywords.insert("private");
fgKeywords.insert("protected");
fgKeywords.insert("public");
fgKeywords.insert("register");
fgKeywords.insert("reinterpret_cast");
fgKeywords.insert("return");
fgKeywords.insert("short");
fgKeywords.insert("signed");
fgKeywords.insert("sizeof");
fgKeywords.insert("static");
fgKeywords.insert("static_cast");
fgKeywords.insert("struct");
fgKeywords.insert("switch");
fgKeywords.insert("template");
fgKeywords.insert("this");
fgKeywords.insert("throw");
fgKeywords.insert("true");
fgKeywords.insert("try");
fgKeywords.insert("typedef");
fgKeywords.insert("typeid");
fgKeywords.insert("typename");
fgKeywords.insert("union");
fgKeywords.insert("unsigned");
fgKeywords.insert("using");
fgKeywords.insert("virtual");
fgKeywords.insert("void");
fgKeywords.insert("volatile");
fgKeywords.insert("wchar_t");
fgKeywords.insert("while");
}
//______________________________________________________________________________
TClass* TDocParser::IsDirective(const TString& line, Ssiz_t pos,
const TString& word, Bool_t& begin) const
{
// return whether word at line's pos is a valid directive, and returns its
// TDocDirective's TClass object, or 0 if it's not a directive. Set begin
// to kTRUE for "Begin_..."
// You can implement your own handlers by implementing a class deriving
// from TDocHandler, and calling it TDocTagDirective for "BEGIN_TAG",
// "END_TAG" blocks.
// '"' serves as escape char
if (pos > 0 && line[pos - 1] == '"')
return 0;
begin = word.BeginsWith("begin_", TString::kIgnoreCase);
Bool_t end = word.BeginsWith("end_", TString::kIgnoreCase);
if (!begin && !end)
return 0;
/* NO - we can have "BEGIN_HTML / * ..."
if (!InContext(kComment))
return 0;
*/
TString tag = word( begin ? 6 : 4, word.Length());
if (!tag.Length())
return 0;
tag.ToLower();
tag[0] -= 'a' - 'A'; // first char is caps
tag.Prepend("TDoc");
tag += "Directive";
TClass* clDirective = TClass::GetClass(tag, kFALSE);
if (gDebug > 0 && !clDirective)
Warning("IsDirective", "Unknown THtml directive %s in line %d!", word.Data(), fLineNo);
return clDirective;
}
//______________________________________________________________________________
Bool_t TDocParser::IsName(UChar_t c)
{
// Check if c is a valid C++ name character
//
//
// Input: c - a single character
//
// Output: TRUE if c is a valid C++ name character
// and FALSE if it's not.
//
// NOTE: Valid name characters are [a..zA..Z0..9_~],
//
Bool_t ret = kFALSE;
if (isalnum(c) || c == '_' || c == '~')
ret = kTRUE;
return ret;
}
//______________________________________________________________________________
Bool_t TDocParser::IsWord(UChar_t c)
{
// Check if c is a valid first character for C++ name
//
//
// Input: c - a single character
//
// Output: TRUE if c is a valid first character for C++ name,
// and FALSE if it's not.
//
// NOTE: Valid first characters are [a..zA..Z_~]
//
Bool_t ret = kFALSE;
if (isalpha(c) || c == '_' || c == '~')
ret = kTRUE;
return ret;
}
//______________________________________________________________________________
TMethod* TDocParser::LocateMethodInCurrentLine(Ssiz_t &posMethodName, TString& ret,
TString& name, TString& params,
Bool_t& isconst, std::ostream &srcOut,
TString &anchor, std::ifstream& sourceFile,
Bool_t allowPureVirtual)
{
// Search for a method starting at posMethodName, and return its return type,
// its name, and its arguments. If the end of arguments is not found in the
// current line, get a new line from sourceFile, beautify it to srcOut, creating
// an anchor as necessary. When this function returns, posMethodName points to the
// end of the function declaration, i.e. right after the arguments' closing bracket.
// If posMethodName == kNPOS, we look for the first matching method in fMethodCounts.
typedef std::map MethodCount_t;
isconst = false;
if (posMethodName == kNPOS) {
name.Remove(0);
TMethod * meth = 0;
Ssiz_t posBlock = fLineRaw.Index('{');
Ssiz_t posQuote = fLineRaw.Index('"');
if (posQuote != kNPOS && (posBlock == kNPOS || posQuote < posBlock))
posBlock = posQuote;
if (posBlock == kNPOS)
posBlock = fLineRaw.Length();
for (MethodCount_t::iterator iMethodName = fMethodCounts.begin();
!name.Length() && iMethodName != fMethodCounts.end(); ++iMethodName) {
TString lookFor(iMethodName->first);
posMethodName = fLineRaw.Index(lookFor);
if (posMethodName != kNPOS && posMethodName < posBlock
&& (posMethodName == 0 || !IsWord(fLineRaw[posMethodName - 1]))) {
// check whether the method name is followed by optional spaces and
// an opening parathesis
Ssiz_t posMethodEnd = posMethodName + lookFor.Length();
while (isspace((UChar_t)fLineRaw[posMethodEnd])) ++posMethodEnd;
if (fLineRaw[posMethodEnd] == '(') {
meth = LocateMethodInCurrentLine(posMethodName, ret, name, params, isconst,
srcOut, anchor, sourceFile, allowPureVirtual);
if (name.Length())
return meth;
}
}
}
return 0;
}
name = fLineRaw(posMethodName, fLineRaw.Length() - posMethodName);
// extract return type
ret = fLineRaw(0, posMethodName);
if (ret.Length()) {
while (ret.Length() && (IsName(ret[ret.Length() - 1]) || ret[ret.Length()-1] == ':'))
ret.Remove(ret.Length() - 1, 1);
Strip(ret);
Bool_t didSomething = kTRUE;
while (didSomething) {
didSomething = kFALSE;
if (ret.BeginsWith("inline ")) {
didSomething = kTRUE;
ret.Remove(0, 7);
}
if (ret.BeginsWith("static ")) {
didSomething = kTRUE;
ret.Remove(0, 7);
}
if (ret.BeginsWith("virtual ")) {
didSomething = kTRUE;
ret.Remove(0, 8);
}
} // while replacing static, virtual, inline
Strip(ret);
}
// extract parameters
Ssiz_t posParam = name.First('(');
if (posParam == kNPOS ||
// no strange return types, please
ret.Contains("{") || ret.Contains("}") || ret.Contains("(") || ret.Contains(")")
|| ret.Contains("=")) {
ret.Remove(0);
name.Remove(0);
params.Remove(0);
return 0;
}
if (name.BeginsWith("operator")) {
// op () (...)
Ssiz_t checkOpBracketParam = posParam + 1;
while (isspace((UChar_t)name[checkOpBracketParam]))
++checkOpBracketParam;
if (name[checkOpBracketParam] == ')') {
++checkOpBracketParam;
while (isspace((UChar_t)name[checkOpBracketParam]))
++checkOpBracketParam;
if (name[checkOpBracketParam] == '(')
posParam = checkOpBracketParam;
}
} // check for op () (...)
if (posParam == kNPOS) {
ret.Remove(0);
name.Remove(0);
params.Remove(0);
return 0;
}
params = name(posParam, name.Length() - posParam);
name.Remove(posParam);
while (name.Length() && isspace((UChar_t)name[name.Length() - 1]))
name.Remove(name.Length() - 1);
if (!name.Length()) {
ret.Remove(0);
name.Remove(0);
params.Remove(0);
return 0;
}
MethodCount_t::const_iterator iMethodName = fMethodCounts.find(name.Data());
if (iMethodName == fMethodCounts.end() || iMethodName->second <= 0) {
ret.Remove(0);
name.Remove(0);
params.Remove(0);
return 0;
}
// find end of param
Ssiz_t posParamEnd = 1;
Int_t bracketLevel = 1;
while (bracketLevel) {
const char* paramEnd = strpbrk(params.Data() + posParamEnd, ")(\"'");
if (!paramEnd) {
// func with params over multiple lines
// gotta write out this line before it gets lost
if (!anchor.Length()) {
// request an anchor, just in case...
AnchorFromLine(fLineStripped, anchor);
if (srcOut)
srcOut << "";
}
++fLineNumber;
if (srcOut)
WriteSourceLine(srcOut);
fLineRaw.ReadLine(sourceFile, kFALSE);
if (sourceFile.eof()) {
Error("LocateMethodInCurrentLine",
"Cannot find end of signature for function %s!",
name.Data());
break;
}
fCommentAtBOL = kFALSE;
// replace class names etc
fLineStripped = fLineRaw;
Strip(fLineStripped);
fLineSource = fLineRaw;
DecorateKeywords(fLineSource);
posParamEnd = params.Length();
params += fLineRaw;
} else
posParamEnd = paramEnd - params.Data();
switch (params[posParamEnd]) {
case '(': ++bracketLevel; ++posParamEnd; break;
case ')': --bracketLevel; ++posParamEnd; break;
case '"': // skip ")"
++posParamEnd;
while (params.Length() > posParamEnd && params[posParamEnd] != '"') {
// skip '\"'
if (params[posParamEnd] == '\\') ++posParamEnd;
++posParamEnd;
}
if (params.Length() <= posParamEnd) {
// something is seriously wrong - skip :-/
ret.Remove(0);
name.Remove(0);
params.Remove(0);
return 0;
}
++posParamEnd; // skip trailing '"'
break;
case '\'': // skip ')'
++posParamEnd;
if (params[posParamEnd] == '\\') ++posParamEnd;
posParamEnd += 2;
break;
default:
++posParamEnd;
}
} // while bracketlevel, i.e. (...(..)...)
{
TString pastParams(params(posParamEnd, params.Length()));
pastParams = pastParams.Strip(TString::kLeading);
isconst = pastParams.BeginsWith("const") && !(isalnum(pastParams[5]) || pastParams[5] == '_');
}
Ssiz_t posBlock = params.Index('{', posParamEnd);
Ssiz_t posSemicolon = params.Index(';', posParamEnd);
Ssiz_t posPureVirt = params.Index('=', posParamEnd);
if (posSemicolon != kNPOS)
if ((posBlock == kNPOS || (posSemicolon < posBlock)) &&
(posPureVirt == kNPOS || !allowPureVirtual)
&& !allowPureVirtual) // allow any "func();" if pv is allowed
params.Remove(0);
if (params.Length())
params.Remove(posParamEnd);
if (!params.Length()) {
ret.Remove(0);
name.Remove(0);
return 0;
}
// update posMethodName to point behind the method
posMethodName = posParam + posParamEnd;
if (fCurrentClass) {
TMethod* meth = fCurrentClass->GetMethodAny(name);
if (meth) {
fDirectiveCount = 0;
fCurrentMethodTag = name + "_";
fCurrentMethodTag += fMethodCounts[name.Data()];
return meth;
}
}
return 0;
}
//______________________________________________________________________________
void TDocParser::Parse(std::ostream& out)
{
// Locate methods, starting in the source file, then inline, then
// immediately inside the class declaration. While doing that also
// find the class description and special tags like the macro tag etc.
fClassDocState = kClassDoc_LookingNothingFound;
DeleteDirectiveOutput();
LocateMethodsInSource(out);
LocateMethodsInHeaderInline(out);
LocateMethodsInHeaderClassDecl(out);
if (!fSourceInfo[kInfoLastUpdate].Length()) {
TDatime date;
fSourceInfo[kInfoLastUpdate] = date.AsString();
}
}
//______________________________________________________________________________
void TDocParser::LocateMethods(std::ostream& out, const char* filename,
Bool_t lookForSourceInfo /*= kTRUE*/,
Bool_t useDocxxStyle /*= kFALSE*/,
Bool_t allowPureVirtual /*= kFALSE*/,
const char* methodPattern /*= 0*/,
const char* sourceExt /*= 0 */)
{
// Collect methods from the source or header file called filename.
// It generates a beautified version of the source file on the fly;
// the output file is given by the fCurrentClass's name, and sourceExt.
// Documentation is extracted to out.
// lookForSourceInfo: if set, author, lastUpdate, and copyright are
// extracted (i.e. the values contained in fSourceInfo)
// useDocxxStyle: if set, documentation can be in front of the method
// name, not only inside the method. Useful doc Doc++/Doxygen style,
// and inline methods.
// lookForClassDescr: if set, the first line matching the class description
// rules is assumed to be the class description for fCurrentClass; the
// description is written to out.
// methodPattern: if set, methods have to be prepended by this tag. Usually
// the class name + "::". In header files, looking for in-place function
// definitions, this should be 0. In that case, only functions in
// fMethodCounts are searched for.
TString sourceFileName(filename);
fCurrentFile = filename;
if (!sourceFileName.Length()) {
fHtml->GetImplFileName(fCurrentClass, kFALSE, sourceFileName);
Error("LocateMethods", "Can't find source file '%s' for class %s!",
sourceFileName.Data(), fCurrentClass->GetName());
return;
}
ifstream sourceFile(sourceFileName.Data());
if (!sourceFile || !sourceFile.good()) {
Error("LocateMethods", "Can't open file '%s' for reading!", sourceFileName.Data());
return;
}
TPMERegexp patternRE(methodPattern ? methodPattern : "");
TString codeOneLiner;
TString methodRet;
TString methodName;
TString methodParam;
Bool_t methodIsConst = kFALSE;
TString anchor;
TString docxxComment;
Bool_t wroteMethodNowWaitingForOpenBlock = kFALSE;
std::ofstream srcHtmlOut;
TString srcHtmlOutName;
if (sourceExt && sourceExt[0]) {
static_cast(fDocOutput)->CreateSourceOutputStream(srcHtmlOut, sourceExt, srcHtmlOutName);
fLineNumber = 0;
} else {
sourceExt = 0;
srcHtmlOutName = fCurrentClass->GetName();
fDocOutput->NameSpace2FileName(srcHtmlOutName);
gSystem->PrependPathName("src", srcHtmlOutName);
srcHtmlOutName += ".h.html";
}
fParseContext.clear();
fParseContext.push_back(kCode);
fDocContext = kIgnore;
fLineNo = 0;
while (!sourceFile.eof()) {
Bool_t needAnchor = kFALSE;
++fLineNo; // we count fortrany
fLineRaw.ReadLine(sourceFile, kFALSE);
if (sourceFile.eof()) break;
fCommentAtBOL = InContext(kComment);
// replace class names etc
fLineStripped = fLineRaw;
Strip(fLineStripped);
fLineSource = fLineRaw;
fLineComment = "";
DecorateKeywords(fLineSource);
if (!ProcessComment()) {
// not a commented line
if (fDocContext == kDocClass && fClassDocState < kClassDoc_Written) {
TString strippedComment(fComment);
Strip(strippedComment);
if (strippedComment.Length() > 0) {
fLastClassDoc = fComment;
if (fClassDocState == kClassDoc_LookingNothingFound) {
fFirstClassDoc = fComment;
fClassDocState = kClassDoc_LookingHaveSomething;
}
}
fDocContext = kIgnore;
}
Ssiz_t impIdx = fLineStripped.Index("ClassImp(");
if (impIdx == 0 && fClassDocState == kClassDoc_LookingHaveSomething) {
TString name(fCurrentClass->GetName());
// take unscoped version
Ssiz_t posLastScope = kNPOS;
while ((posLastScope = name.Index("::")) != kNPOS)
name.Remove(0, posLastScope + 2);
Ssiz_t posName = fLineStripped.Index(name, impIdx);
if (posName != kNPOS) {
Ssiz_t posClosingParen = posName + name.Length();
while (isspace(fLineStripped[posClosingParen])) ++posClosingParen;
if (fLineStripped[posClosingParen] == ')') {
WriteClassDoc(out, kFALSE);
fDocContext = kIgnore;
}
}
}
if (fLineStripped.Length())
// remove last class doc if it not followed by ClassImp
// (with optional empty lines in between)
fLastClassDoc = "";
// write previous method
if (methodName.Length() && !wroteMethodNowWaitingForOpenBlock) {
TString savedComment;
if (useDocxxStyle && docxxComment.Length()) {
savedComment = fComment;
fComment = docxxComment;
}
WriteMethod(out, methodRet, methodName, methodParam, methodIsConst,
gSystem->BaseName(srcHtmlOutName), anchor, codeOneLiner);
docxxComment.Remove(0);
if (savedComment[0]) {
fComment = savedComment;
}
}
if (!wroteMethodNowWaitingForOpenBlock) {
// check for method
Ssiz_t posPattern = kNPOS;
if (methodPattern) {
posPattern = fLineRaw.Index((TPRegexp&)patternRE);
}
if (posPattern != kNPOS && methodPattern) {
// no strings, no blocks in front of function declarations / implementations
static const char vetoChars[] = "{\"";
for (int ich = 0; posPattern != kNPOS && vetoChars[ich]; ++ich) {
Ssiz_t posVeto = fLineRaw.Index(vetoChars[ich]);
if (posVeto != kNPOS && posVeto < posPattern)
posPattern = kNPOS;
}
}
if (posPattern != kNPOS || !methodPattern) {
if (methodPattern) {
patternRE.Match(fLineRaw);
posPattern += patternRE[0].Length();
}
LocateMethodInCurrentLine(posPattern, methodRet, methodName,
methodParam, methodIsConst, srcHtmlOut,
anchor, sourceFile, allowPureVirtual);
if (methodName.Length()) {
fDocContext = kDocFunc;
needAnchor = !anchor.Length();
if (useDocxxStyle)
docxxComment = fComment;
fComment.Remove(0);
codeOneLiner.Remove(0);
wroteMethodNowWaitingForOpenBlock = fLineRaw.Index("{", posPattern) == kNPOS;
wroteMethodNowWaitingForOpenBlock &= fLineRaw.Index(";", posPattern) == kNPOS;
} else if (fLineRaw.First("{};") != kNPOS)
// these chars reset the preceding comment
fComment.Remove(0);
} // pattern matches - could be a method
else
fComment.Remove(0);
} else {
wroteMethodNowWaitingForOpenBlock &= fLineRaw.Index("{") == kNPOS;
wroteMethodNowWaitingForOpenBlock &= fLineRaw.Index(";") == kNPOS;
} // if !wroteMethodNowWaitingForOpenBlock
if (methodName.Length() && !wroteMethodNowWaitingForOpenBlock) {
// make sure we don't have more '{' in commentLine than in fLineRaw
if (!codeOneLiner.Length() &&
fLineSource.CountChar('{') == 1 &&
fLineSource.CountChar('}') == 1) {
// a one-liner
codeOneLiner = fLineSource;
codeOneLiner.Remove(0, codeOneLiner.Index('{'));
codeOneLiner.Remove(codeOneLiner.Index('}') + 1);
}
} // if method name and '{'
// else not a comment, and we don't need the previous one:
else if (!methodName.Length() && !useDocxxStyle)
fComment.Remove(0);
if (needAnchor || fExtraLinesWithAnchor.find(fLineNo) != fExtraLinesWithAnchor.end()) {
AnchorFromLine(fLineStripped, anchor);
if (sourceExt)
srcHtmlOut << "";
}
// else anchor.Remove(0); - NO! WriteMethod will need it later!
} // if !comment
// check for last update,...
Ssiz_t posTag = kNPOS;
if (lookForSourceInfo)
for (Int_t si = 0; si < (Int_t) kNumSourceInfos; ++si)
if (!fSourceInfo[si].Length() && (posTag = fLineRaw.Index(fSourceInfoTags[si])) != kNPOS) {
fSourceInfo[si] = fLineRaw(posTag + strlen(fSourceInfoTags[si]), fLineRaw.Length() - posTag);
if (si == kInfoAuthor)
fDocOutput->FixupAuthorSourceInfo(fSourceInfo[kInfoAuthor]);
}
// write to .cxx.html
++fLineNumber;
if (srcHtmlOut)
WriteSourceLine(srcHtmlOut);
else if (needAnchor)
fExtraLinesWithAnchor.insert(fLineNo);
} // while !sourceFile.eof()
// deal with last func
if (methodName.Length()) {
if (useDocxxStyle && docxxComment.Length())
fComment = docxxComment;
WriteMethod(out, methodRet, methodName, methodParam, methodIsConst,
gSystem->BaseName(srcHtmlOutName), anchor, codeOneLiner);
docxxComment.Remove(0);
} else
WriteClassDoc(out);
srcHtmlOut << "" << std::endl;
fDocOutput->WriteLineNumbers(srcHtmlOut, fLineNumber, gSystem->BaseName(fCurrentFile));
srcHtmlOut << "" << std::endl;
fDocOutput->WriteHtmlFooter(srcHtmlOut, "../");
fParseContext.clear();
fParseContext.push_back(kCode);
fDocContext = kIgnore;
fCurrentFile = "";
}
//______________________________________________________________________________
void TDocParser::LocateMethodsInSource(std::ostream& out)
{
// Given fCurrentClass, look for methods in its source file,
// and extract documentation to out, while beautifying the source
// file in parallel.
// for Doc++ style
Bool_t useDocxxStyle = (fHtml->GetDocStyle() == "Doc++");
TString pattern(fCurrentClass->GetName());
// take unscoped version
Ssiz_t posLastScope = kNPOS;
while ((posLastScope = pattern.Index("::")) != kNPOS)
pattern.Remove(0, posLastScope + 2);
pattern += "::";
TString implFileName;
if (fHtml->GetImplFileName(fCurrentClass, kTRUE, implFileName)) {
LocateMethods(out, implFileName, kFALSE /*source info*/, useDocxxStyle,
kFALSE /*allowPureVirtual*/, pattern, ".cxx.html");
Ssiz_t posGt = pattern.Index('>');
if (posGt != kNPOS) {
// template! Re-run with pattern '...<.*>::'
Ssiz_t posLt = pattern.Index('<');
if (posLt != kNPOS && posLt < posGt) {
pattern.Replace(posLt + 1, posGt - posLt - 1, ".*");
LocateMethods(out, implFileName, kFALSE /*source info*/, useDocxxStyle,
kFALSE /*allowPureVirtual*/, pattern, ".cxx.html");
}
}
}
}
//______________________________________________________________________________
void TDocParser::LocateMethodsInHeaderInline(std::ostream& out)
{
// Given fCurrentClass, look for methods in its header file,
// and extract documentation to out.
// for inline methods, always allow doc before func
Bool_t useDocxxStyle = kTRUE;
TString pattern(fCurrentClass->GetName());
// take unscoped version
Ssiz_t posLastScope = kNPOS;
while ((posLastScope = pattern.Index("::")) != kNPOS)
pattern.Remove(0, posLastScope + 1);
pattern += "::";
TString declFileName;
if (fHtml->GetDeclFileName(fCurrentClass, kTRUE, declFileName)) {
LocateMethods(out, declFileName, kTRUE /*source info*/, useDocxxStyle,
kFALSE /*allowPureVirtual*/, pattern, 0);
Ssiz_t posGt = pattern.Index('>');
if (posGt != kNPOS) {
// template! Re-run with pattern '...<.*>::'
Ssiz_t posLt = pattern.Index('<');
if (posLt != kNPOS && posLt < posGt) {
pattern.Replace(posLt + 1, posGt - posLt - 1, ".*");
LocateMethods(out, declFileName, kTRUE /*source info*/, useDocxxStyle,
kFALSE /*allowPureVirtual*/, pattern, 0);
}
}
}
}
//______________________________________________________________________________
void TDocParser::LocateMethodsInHeaderClassDecl(std::ostream& out)
{
// Given fCurrentClass, look for methods in its header file's
// class declaration block, and extract documentation to out,
// while beautifying the header file in parallel.
TString declFileName;
if (fHtml->GetDeclFileName(fCurrentClass, kTRUE, declFileName))
LocateMethods(out, declFileName, kTRUE/*source info*/, kTRUE /*useDocxxStyle*/,
kTRUE /*allowPureVirtual*/, 0, ".h.html");
}
//______________________________________________________________________________
Bool_t TDocParser::ProcessComment()
{
// Parse the current line as a comment, handling directives and re-formatting
// the comment: remove "/*", "*/", "//", similar characters surrounding lines,
// etc.
//
// Return kFALSE if the line is not a comment.
if (!fCommentAtBOL
&& !(fLineStripped[0] == '/'
&& (fLineStripped[1] == '/' || fLineStripped[1] == '*'))
&& !InContext(kComment) && !InContext(kDirective)) {
fLineComment = "";
return kFALSE;
}
//if (InContext(kDirective))
// return kTRUE; - NO! we might have a comment from a previous directive!
// don't write out empty lines if the current directive is eating the line
if (InContext(kDirective) && !fLineComment.Length())
return kTRUE;
TString commentLine(fLineComment.Strip());
// remove all