+// ---------------------------------------------------------------------------
+// DocManager
+// ---------------------------------------------------------------------------
+
+DocManager::DocManager(bool checkParamNames)
+{
+ m_checkParamNames = checkParamNames;
+}
+
+size_t DocManager::TryMatch(const char *str, const char *match)
+{
+ size_t lenMatch = 0;
+ while ( str[lenMatch] == match[lenMatch] ) {
+ lenMatch++;
+
+ if ( match[lenMatch] == '\0' )
+ return lenMatch;
+ }
+
+ return 0;
+}
+
+bool DocManager::SkipUntil(const char **pp, char c)
+{
+ const char *p = *pp;
+ while ( *p != c ) {
+ if ( *p == '\0' )
+ break;
+
+ if ( *p == '\n' )
+ m_line++;
+
+ p++;
+ }
+
+ *pp = p;
+
+ return *p == c;
+}
+
+bool DocManager::SkipSpaceUntil(const char **pp, char c)
+{
+ const char *p = *pp;
+ while ( *p != c ) {
+ if ( !isspace(*p) || *p == '\0' )
+ break;
+
+ if ( *p == '\n' )
+ m_line++;
+
+ p++;
+ }
+
+ *pp = p;
+
+ return *p == c;
+}
+
+wxString DocManager::ExtractStringBetweenBraces(const char **pp)
+{
+ wxString result;
+
+ if ( !SkipSpaceUntil(pp, '{') ) {
+ wxLogWarning("file %s(%d): '{' expected after '\\param'",
+ m_filename.c_str(), m_line);
+
+ }
+ else {
+ const char *startParam = ++*pp; // skip '{'
+
+ if ( !SkipUntil(pp, '}') ) {
+ wxLogWarning("file %s(%d): '}' expected after '\\param'",
+ m_filename.c_str(), m_line);
+ }
+ else {
+ result = wxString(startParam, (*pp)++ - startParam);
+ }
+ }
+
+ return result;
+}
+
+bool DocManager::ParseTeXFile(const wxString& filename)
+{
+ m_filename = filename;
+
+ wxFile file(m_filename, wxFile::read);
+ if ( !file.IsOpened() )
+ return FALSE;
+
+ off_t len = file.Length();
+ if ( len == wxInvalidOffset )
+ return FALSE;
+
+ char *buf = new char[len + 1];
+ buf[len] = '\0';
+
+ if ( file.Read(buf, len) == wxInvalidOffset ) {
+ delete [] buf;
+
+ return FALSE;
+ }
+
+ // reinit everything
+ m_line = 1;
+
+ wxLogVerbose("%s: starting to parse doc file '%s'.",
+ GetCurrentTime("%H:%M:%S"), m_filename.c_str());
+
+ // the name of the class from the last "\membersection" command: we assume
+ // that the following "\func" or "\constfunc" always documents a method of
+ // this class (and it should always be like that in wxWindows documentation)
+ wxString classname;
+
+ for ( const char *current = buf; current - buf < len; current++ ) {
+ // FIXME parsing is awfully inefficient
+
+ if ( *current == '%' ) {
+ // comment, skip until the end of line
+ current++;
+ SkipUntil(¤t, '\n');
+
+ continue;
+ }
+
+ // all the command we're interested in start with '\\'
+ while ( *current != '\\' && *current != '\0' ) {
+ if ( *current++ == '\n' )
+ m_line++;
+ }
+
+ if ( *current == '\0' ) {
+ // no more TeX commands left
+ break;
+ }
+
+ current++; // skip '\\'
+
+ enum
+ {
+ Nothing,
+ Func,
+ ConstFunc,
+ MemberSect
+ } foundCommand = Nothing;
+
+ size_t lenMatch = TryMatch(current, "func");
+ if ( lenMatch ) {
+ foundCommand = Func;
+ }
+ else {
+ lenMatch = TryMatch(current, "constfunc");
+ if ( lenMatch )
+ foundCommand = ConstFunc;
+ else {
+ lenMatch = TryMatch(current, "membersection");
+
+ if ( lenMatch )
+ foundCommand = MemberSect;
+ }
+ }
+
+ if ( foundCommand == Nothing )
+ continue;
+
+ current += lenMatch;
+
+ if ( !SkipSpaceUntil(¤t, '{') ) {
+ wxLogWarning("file %s(%d): '{' expected after \\func, "
+ "\\constfunc or \\membersection.",
+ m_filename.c_str(), m_line);
+
+ continue;
+ }
+
+ current++;
+
+ if ( foundCommand == MemberSect ) {
+ // what follows has the form <classname>::<funcname>
+ const char *startClass = current;
+ if ( !SkipUntil(¤t, ':') || *(current + 1) != ':' ) {
+ wxLogWarning("file %s(%d): '::' expected after "
+ "\\membersection.", m_filename.c_str(), m_line);
+ }
+ else {
+ classname = wxString(startClass, current - startClass);
+ TeXUnfilter(&classname);
+ }
+
+ continue;
+ }
+
+ // extract the return type
+ const char *startRetType = current;
+
+ if ( !SkipUntil(¤t, '}') ) {
+ wxLogWarning("file %s(%d): '}' expected after return type",
+ m_filename.c_str(), m_line);
+
+ continue;
+ }
+
+ wxString returnType = wxString(startRetType, current - startRetType);
+ TeXUnfilter(&returnType);
+
+ current++;
+ if ( !SkipSpaceUntil(¤t, '{') ) {
+ wxLogWarning("file %s(%d): '{' expected after return type",
+ m_filename.c_str(), m_line);
+
+ continue;
+ }
+
+ current++;
+ const char *funcEnd = current;
+ if ( !SkipUntil(&funcEnd, '}') ) {
+ wxLogWarning("file %s(%d): '}' expected after function name",
+ m_filename.c_str(), m_line);
+
+ continue;
+ }
+
+ wxString funcName = wxString(current, funcEnd - current);
+ current = funcEnd + 1;
+
+ // trim spaces from both sides
+ funcName.Trim(FALSE);
+ funcName.Trim(TRUE);
+
+ // special cases: '$...$' may be used for LaTeX inline math, remove the
+ // '$'s
+ if ( funcName.Find('$') != wxNOT_FOUND ) {
+ wxString name;
+ for ( const char *p = funcName.c_str(); *p != '\0'; p++ ) {
+ if ( *p != '$' && !isspace(*p) )
+ name += *p;
+ }
+
+ funcName = name;
+ }
+
+ // \destruct{foo} is really ~foo
+ if ( funcName[0u] == '\\' ) {
+ size_t len = strlen("\\destruct{");
+ if ( funcName(0, len) != "\\destruct{" ) {
+ wxLogWarning("file %s(%d): \\destruct expected",
+ m_filename.c_str(), m_line);
+
+ continue;
+ }
+
+ funcName.erase(0, len);
+ funcName.Prepend('~');
+
+ if ( !SkipSpaceUntil(¤t, '}') ) {
+ wxLogWarning("file %s(%d): '}' expected after destructor",
+ m_filename.c_str(), m_line);
+
+ continue;
+ }
+
+ funcEnd++; // there is an extra '}' to count
+ }
+
+ TeXUnfilter(&funcName);
+
+ // extract params
+ current = funcEnd + 1; // skip '}'
+ if ( !SkipSpaceUntil(¤t, '{') ||
+ (current++, !SkipSpaceUntil(¤t, '\\')) ) {
+ wxLogWarning("file %s(%d): '\\param' or '\\void' expected",
+ m_filename.c_str(), m_line);
+
+ continue;
+ }
+
+ wxArrayString paramNames, paramTypes, paramValues;
+
+ bool isVararg = FALSE;
+
+ current++; // skip '\\'
+ lenMatch = TryMatch(current, "void");
+ if ( !lenMatch ) {
+ lenMatch = TryMatch(current, "param");
+ while ( lenMatch && (current - buf < len) ) {
+ current += lenMatch;
+
+ // now come {paramtype}{paramname}
+ wxString paramType = ExtractStringBetweenBraces(¤t);
+ if ( !!paramType ) {
+ wxString paramText = ExtractStringBetweenBraces(¤t);
+ if ( !!paramText ) {
+ // the param declaration may contain default value
+ wxString paramName = paramText.BeforeFirst('='),
+ paramValue = paramText.AfterFirst('=');
+
+ // sanitize all strings
+ TeXUnfilter(¶mValue);
+ TeXUnfilter(¶mName);
+ TeXUnfilter(¶mType);
+
+ paramValues.Add(paramValue);
+ paramNames.Add(paramName);
+ paramTypes.Add(paramType);
+ }
+ }
+ else {
+ // vararg function?
+ wxString paramText = ExtractStringBetweenBraces(¤t);
+ if ( paramText == "..." ) {
+ isVararg = TRUE;
+ }
+ else {
+ wxLogWarning("Parameters of '%s::%s' are in "
+ "incorrect form.",
+ classname.c_str(), funcName.c_str());
+ }
+ }
+
+ // what's next?
+ current = SkipSpaces(current);
+ if ( *current == ',' || *current == '}' ) {
+ current = SkipSpaces(++current);
+
+ lenMatch = TryMatch(current, "\\param");
+ }
+ else {
+ wxLogWarning("file %s(%d): ',' or '}' expected after "
+ "'\\param'", m_filename.c_str(), m_line);
+
+ continue;
+ }
+ }
+
+ // if we got here there was no '\\void', so must have some params
+ if ( paramNames.IsEmpty() ) {
+ wxLogWarning("file %s(%d): '\\param' or '\\void' expected",
+ m_filename.c_str(), m_line);
+
+ continue;
+ }
+ }
+
+ // verbose diagnostic output
+ wxString paramsAll;
+ size_t param, paramCount = paramNames.GetCount();
+ for ( param = 0; param < paramCount; param++ ) {
+ if ( param != 0 ) {
+ paramsAll << ", ";
+ }
+
+ paramsAll << paramTypes[param] << ' ' << paramNames[param];
+ }
+
+ wxLogVerbose("file %s(%d): found '%s %s::%s(%s)%s'",
+ m_filename.c_str(), m_line,
+ returnType.c_str(),
+ classname.c_str(),
+ funcName.c_str(),
+ paramsAll.c_str(),
+ foundCommand == ConstFunc ? " const" : "");
+
+ // store the info about the just found function
+ ArrayMethodInfo *methods;
+ int index = m_classes.Index(classname);
+ if ( index == wxNOT_FOUND ) {
+ m_classes.Add(classname);
+
+ methods = new ArrayMethodInfo;
+ m_methods.Add(methods);
+ }
+ else {
+ methods = m_methods[(size_t)index];
+ }
+
+ ArrayParamInfo params;
+ for ( param = 0; param < paramCount; param++ ) {
+ params.Add(new ParamInfo(paramTypes[param],
+ paramNames[param],
+ paramValues[param]));
+ }
+
+ MethodInfo *method = new MethodInfo(returnType, funcName, params);
+ if ( foundCommand == ConstFunc )
+ method->SetFlag(MethodInfo::Const);
+ if ( isVararg )
+ method->SetFlag(MethodInfo::Vararg);
+
+ methods->Add(method);
+ }
+
+ delete [] buf;
+
+ wxLogVerbose("%s: finished parsing doc file '%s'.\n",
+ GetCurrentTime("%H:%M:%S"), m_filename.c_str());
+
+ return TRUE;
+}
+
+bool DocManager::DumpDifferences(spContext *ctxTop) const
+{
+ typedef MMemberListT::const_iterator MemberIndex;
+
+ bool foundDiff = FALSE;
+
+ // flag telling us whether the given class was found at all in the header
+ size_t nClass, countClassesInDocs = m_classes.GetCount();
+ bool *classExists = new bool[countClassesInDocs];
+ for ( nClass = 0; nClass < countClassesInDocs; nClass++ ) {
+ classExists[nClass] = FALSE;
+ }
+
+ // ctxTop is normally an spFile
+ wxASSERT( ctxTop->GetContextType() == SP_CTX_FILE );
+
+ const MMemberListT& classes = ctxTop->GetMembers();
+ for ( MemberIndex i = classes.begin(); i != classes.end(); i++ ) {
+ spContext *ctx = *i;
+ if ( ctx->GetContextType() != SP_CTX_CLASS ) {
+ // TODO process also global functions, macros, ...
+ continue;
+ }
+
+ spClass *ctxClass = (spClass *)ctx;
+ const wxString& nameClass = ctxClass->mName;
+ int index = m_classes.Index(nameClass);
+ if ( index == wxNOT_FOUND ) {
+ if ( !m_ignoreNames.IgnoreClass(nameClass) ) {
+ foundDiff = TRUE;
+
+ wxLogError("Class '%s' is not documented at all.",
+ nameClass.c_str());
+ }
+
+ // it makes no sense to check for its functions
+ continue;
+ }
+ else {
+ classExists[index] = TRUE;
+ }
+
+ // array of method descriptions for this class
+ const ArrayMethodInfo& methods = *(m_methods[index]);
+ size_t nMethod, countMethods = methods.GetCount();
+
+ // flags telling if we already processed given function
+ bool *methodExists = new bool[countMethods];
+ for ( nMethod = 0; nMethod < countMethods; nMethod++ ) {
+ methodExists[nMethod] = FALSE;
+ }
+
+ wxArrayString aOverloadedMethods;
+
+ const MMemberListT& functions = ctxClass->GetMembers();
+ for ( MemberIndex j = functions.begin(); j != functions.end(); j++ ) {
+ ctx = *j;
+ if ( ctx->GetContextType() != SP_CTX_OPERATION )
+ continue;
+
+ spOperation *ctxMethod = (spOperation *)ctx;
+ const wxString& nameMethod = ctxMethod->mName;
+
+ // find all functions with the same name
+ wxArrayInt aMethodsWithSameName;
+ for ( nMethod = 0; nMethod < countMethods; nMethod++ ) {
+ if ( methods[nMethod]->GetName() == nameMethod )
+ aMethodsWithSameName.Add(nMethod);
+ }
+
+ if ( aMethodsWithSameName.IsEmpty() && ctxMethod->IsPublic() ) {
+ if ( !m_ignoreNames.IgnoreMethod(nameClass, nameMethod) ) {
+ foundDiff = TRUE;
+
+ wxLogError("'%s::%s' is not documented.",
+ nameClass.c_str(),
+ nameMethod.c_str());
+ }
+
+ // don't check params
+ continue;
+ }
+ else if ( aMethodsWithSameName.GetCount() == 1 ) {
+ index = (size_t)aMethodsWithSameName[0u];
+ methodExists[index] = TRUE;
+
+ if ( m_ignoreNames.IgnoreMethod(nameClass, nameMethod) )
+ continue;
+
+ if ( !ctxMethod->IsPublic() ) {
+ wxLogWarning("'%s::%s' is documented but not public.",
+ nameClass.c_str(),
+ nameMethod.c_str());
+ }
+
+ // check that the flags match
+ const MethodInfo& method = *(methods[index]);
+
+ bool isVirtual = ctxMethod->mIsVirtual;
+ if ( isVirtual != method.HasFlag(MethodInfo::Virtual) ) {
+ wxLogWarning("'%s::%s' is incorrectly documented as %s"
+ "virtual.",
+ nameClass.c_str(),
+ nameMethod.c_str(),
+ isVirtual ? "not " : "");
+ }
+
+ bool isConst = ctxMethod->mIsConstant;
+ if ( isConst != method.HasFlag(MethodInfo::Const) ) {
+ wxLogWarning("'%s::%s' is incorrectly documented as %s"
+ "constant.",
+ nameClass.c_str(),
+ nameMethod.c_str(),
+ isConst ? "not " : "");
+ }
+
+ // check that the params match
+ const MMemberListT& params = ctxMethod->GetMembers();
+
+ if ( params.size() != method.GetParamCount() ) {
+ wxLogError("Incorrect number of parameters for '%s::%s' "
+ "in the docs: should be %d instead of %d.",
+ nameClass.c_str(),
+ nameMethod.c_str(),
+ params.size(), method.GetParamCount());
+ }
+ else {
+ size_t nParam = 0;
+ for ( MemberIndex k = params.begin();
+ k != params.end();
+ k++, nParam++ ) {
+ ctx = *k;
+
+ // what else can a function have?
+ wxASSERT( ctx->GetContextType() == SP_CTX_PARAMETER );
+
+ spParameter *ctxParam = (spParameter *)ctx;
+ const ParamInfo& param = method.GetParam(nParam);
+ if ( m_checkParamNames &&
+ (param.GetName() != ctxParam->mName) ) {
+ foundDiff = TRUE;
+
+ wxLogError("Parameter #%d of '%s::%s' should be "
+ "'%s' and not '%s'.",
+ nParam + 1,
+ nameClass.c_str(),
+ nameMethod.c_str(),
+ ctxParam->mName.c_str(),
+ param.GetName().c_str());
+
+ continue;
+ }
+
+ if ( param.GetType() != ctxParam->mType ) {
+ foundDiff = TRUE;
+
+ wxLogError("Type of parameter '%s' of '%s::%s' "
+ "should be '%s' and not '%s'.",
+ ctxParam->mName.c_str(),
+ nameClass.c_str(),
+ nameMethod.c_str(),
+ ctxParam->mType.c_str(),
+ param.GetType().GetName().c_str());
+
+ continue;
+ }
+
+ if ( param.GetDefValue() != ctxParam->mInitVal ) {
+ wxLogWarning("Default value of parameter '%s' of "
+ "'%s::%s' should be '%s' and not "
+ "'%s'.",
+ ctxParam->mName.c_str(),
+ nameClass.c_str(),
+ nameMethod.c_str(),
+ ctxParam->mInitVal.c_str(),
+ param.GetDefValue().c_str());
+ }
+ }
+ }
+ }
+ else {
+ // TODO OVER add real support for overloaded methods
+
+ if ( m_ignoreNames.IgnoreMethod(nameClass, nameMethod) )
+ continue;
+
+ if ( aOverloadedMethods.Index(nameMethod) == wxNOT_FOUND ) {
+ // mark all methods with this name as existing
+ for ( nMethod = 0; nMethod < countMethods; nMethod++ ) {
+ if ( methods[nMethod]->GetName() == nameMethod )
+ methodExists[nMethod] = TRUE;
+ }
+
+ aOverloadedMethods.Add(nameMethod);
+
+ wxLogVerbose("'%s::%s' is overloaded and I'm too "
+ "stupid to find the right match - skipping "
+ "the param and flags checks.",
+ nameClass.c_str(),
+ nameMethod.c_str());
+ }
+ //else: warning already given
+ }
+ }
+
+ for ( nMethod = 0; nMethod < countMethods; nMethod++ ) {
+ if ( !methodExists[nMethod] ) {
+ const wxString& nameMethod = methods[nMethod]->GetName();
+ if ( !m_ignoreNames.IgnoreMethod(nameClass, nameMethod) ) {
+ foundDiff = TRUE;
+
+ wxLogError("'%s::%s' is documented but doesn't exist.",
+ nameClass.c_str(),
+ nameMethod.c_str());
+ }
+ }
+ }
+
+ delete [] methodExists;
+ }
+
+ // check that all classes we found in the docs really exist
+ for ( nClass = 0; nClass < countClassesInDocs; nClass++ ) {
+ if ( !classExists[nClass] ) {
+ foundDiff = TRUE;
+
+ wxLogError("Class '%s' is documented but doesn't exist.",
+ m_classes[nClass].c_str());
+ }
+ }
+
+ delete [] classExists;
+
+ return !foundDiff;
+}
+
+DocManager::~DocManager()
+{
+ WX_CLEAR_ARRAY(m_methods);
+}
+
+// ---------------------------------------------------------------------------
+// IgnoreNamesHandler implementation
+// ---------------------------------------------------------------------------
+
+int IgnoreNamesHandler::CompareIgnoreListEntries(IgnoreListEntry *first,
+ IgnoreListEntry *second)
+{
+ // first compare the classes
+ int rc = first->m_classname.Cmp(second->m_classname);
+ if ( rc == 0 )
+ rc = first->m_funcname.Cmp(second->m_funcname);
+
+ return rc;
+}
+
+bool IgnoreNamesHandler::AddNamesFromFile(const wxString& filename)
+{
+ wxFile file(filename, wxFile::read);
+ if ( !file.IsOpened() )
+ return FALSE;
+
+ off_t len = file.Length();
+ if ( len == wxInvalidOffset )
+ return FALSE;
+
+ char *buf = new char[len + 1];
+ buf[len] = '\0';
+
+ if ( file.Read(buf, len) == wxInvalidOffset ) {
+ delete [] buf;
+
+ return FALSE;
+ }
+
+ wxString line;
+ for ( const char *current = buf; ; current++ ) {
+#ifdef __WXMSW__
+ // skip DOS line separator
+ if ( *current == '\r' )
+ current++;
+#endif // wxMSW
+
+ if ( *current == '\n' || *current == '\0' ) {
+ if ( line[0u] != '#' ) {
+ if ( line.Find(':') != wxNOT_FOUND ) {
+ wxString classname = line.BeforeFirst(':'),
+ funcname = line.AfterLast(':');
+ m_ignore.Add(new IgnoreListEntry(classname, funcname));
+ }
+ else {
+ // entire class
+ m_ignore.Add(new IgnoreListEntry(line, ""));
+ }
+ }
+ //else: comment
+
+ if ( *current == '\0' )
+ break;
+
+ line.Empty();
+ }
+ else {
+ line += *current;
+ }
+ }
+
+ delete [] buf;
+
+ return TRUE;