]> git.saurik.com Git - wxWidgets.git/blobdiff - utils/HelpGen/src/HelpGen.cpp
cleaning up the mess created by the FloodFill patch
[wxWidgets.git] / utils / HelpGen / src / HelpGen.cpp
index af3023ecdfbb415d3e89bddf5120f7cc52d3aacb..4bf5e4418fa397e70e0efe831609e7754b5803e7 100644 (file)
 /////////////////////////////////////////////////////////////////////////////
 
 /*
-   TODO (+ means fixed)
+   BUGS
+
+    1. wx/string.h confuses C++ parser terribly
+    2. C++ parser doesn't know about virtual functions, nor static ones
+    3. param checking is not done for vararg functions
+    4. type comparison is dumb: it doesn't know that "char *" is the same
+       that "char []" nor that "const char *" is the same as "char const *"
+
+   TODO (+ means fixed), see also the change log at the end of the file.
 
    (i) small fixes in the current version
-   
+
    +1. Quote special TeX characters like '&' and '_' (=> derive from wxFile)
     2. Document typedefs
     3. Document global variables
     4. Document #defines
+   +5. Program options
+    6. Include file name/line number in the "diff" messages?
+   +7. Support for vararg functions
 
    (ii) plans for version 2
     1. Use wxTextFile for direct file access to avoid one scan method problems
-   
+    2. Use command line parser class for the options
+    3. support for overloaded functions in diff mode (search for OVER)
+
+   (iii) plans for version 3
+    1. Merging with existing files
+    2. GUI
 */
 
 // =============================================================================
 // wxWindows
 #include "wx/wxprec.h"
 
+#if wxUSE_GUI
+    #error "This is a console program and can be only compiled using wxBase"
+#endif
+
 #ifndef WX_PRECOMP
-    #include <wx/string.h>
-    #include <wx/log.h>
-    #include <wx/file.h>
+    #include "wx/string.h"
+    #include "wx/log.h"
+    #include "wx/dynarray.h"
+    #include "wx/wx.h"
 #endif // WX_PRECOMP
 
+#include "wx/file.h"
+#include "wx/regex.h"
+#include "wx/hash.h"
+
 // C++ parsing classes
 #include "cjparser.h"
 
 #include <stdio.h>
 #include <time.h>
 
+// argh, Windows defines this
+#ifdef GetCurrentTime
+#undef GetCurrentTime
+#endif
+
+// -----------------------------------------------------------------------------
+// global vars
+// -----------------------------------------------------------------------------
+
+class HelpGenApp: public wxApp
+{
+public:
+    HelpGenApp() {};
+
+    // don't let wxWin parse our cmd line, we do it ourselves
+    virtual bool OnInit() { return TRUE; }
+
+    virtual int OnRun();
+};
+
+// IMPLEMENT_APP(HelpGenApp);
+
 // -----------------------------------------------------------------------------
 // private functions
 // -----------------------------------------------------------------------------
 
-// return the label for the given function name
-static wxString MakeLabel(const char *classname, const char *funcname);
-    
-// quotes special TeX characters in place
+// return the label for the given function name (i.e. argument of \label)
+static wxString MakeLabel(const char *classname, const char *funcname = NULL);
+
+// return the whole \helpref{arg}{arg_label} string
+static wxString MakeHelpref(const char *argument);
+
+// [un]quote special TeX characters (in place)
 static void TeXFilter(wxString* str);
+static void TeXUnfilter(wxString* str); // also trims spaces
+
+// get all comments associated with this context
+static wxString GetAllComments(const spContext& ctx);
+
+// get the string with current time (returns pointer to static buffer)
+// timeFormat is used for the call of strftime(3)
+static const char *GetCurrentTime(const char *timeFormat);
+
+// get the string containing the program version
+static const wxString GetVersionString();
 
 // -----------------------------------------------------------------------------
 // private classes
 // -----------------------------------------------------------------------------
 
-// add a function which sanitazes the string before writing it to the file
+// a function documentation entry
+struct FunctionDocEntry
+{
+    FunctionDocEntry(const wxString& name_, const wxString& text_)
+        : name(name_), text(text_) { }
+
+    // the function name
+    wxString name;
+
+    // the function doc text
+    wxString text;
+
+    // sorting stuff
+    static int Compare(FunctionDocEntry **pp1, FunctionDocEntry **pp2)
+    {
+        // the methods should appear in the following order: ctors, dtor, all
+        // the rest in the alphabetical order
+        bool isCtor1 = (*pp1)->name == classname;
+        bool isCtor2 = (*pp2)->name == classname;
+
+        if ( isCtor1 ) {
+            if ( isCtor2 ) {
+                // we don't order the ctors because we don't know how to do it
+                return 0;
+            }
+
+            // ctor comes before non-ctor
+            return -1;
+        }
+        else {
+            if ( isCtor2 ) {
+                // non-ctor must come after ctor
+                return 1;
+            }
+
+            wxString dtorname = wxString('~') + classname;
+
+            // there is only one dtor, so the logic here is simpler
+            if ( (*pp1)->name == dtorname ) {
+                return -1;
+            }
+            else if ( (*pp2)->name == dtorname ) {
+                return 1;
+            }
+
+            // two normal methods
+            return strcmp((*pp1)->name, (*pp2)->name);
+        }
+    }
+
+    static wxString classname;
+};
+
+wxString FunctionDocEntry::classname;
+
+WX_DECLARE_OBJARRAY(FunctionDocEntry, FunctionDocEntries);
+
+#include "wx/arrimpl.cpp"
+
+WX_DEFINE_OBJARRAY(FunctionDocEntries);
+
+// add a function which sanitazes the string before writing it to the file and
+// also capable of delaying output and sorting it before really writing it to
+// the file (done from FlushAll())
 class wxTeXFile : public wxFile
 {
 public:
-    wxTeXFile() : wxFile() { }
+    wxTeXFile() { }
+
+    // write a string to file verbatim (should only be used for the strings
+    // inside verbatim environment)
+    void WriteVerbatim(const wxString& s)
+    {
+        m_text += s;
+    }
 
-    bool WriteTeX(const wxString& s)
+    // write a string quoting TeX specials in it
+    void WriteTeX(const wxString& s)
     {
         wxString t(s);
         TeXFilter(&t);
 
-        return wxFile::Write(t);
+        m_text += t;
+    }
+
+    // do write everything to file
+    bool FlushAll()
+    {
+        if ( m_text.empty() )
+            return TRUE;
+
+        if ( !Write(m_text) ) {
+            wxLogError("Failed to output generated documentation.");
+
+            return FALSE;
+        }
+
+        m_text.clear();
+
+        return TRUE;
+    }
+
+private:
+    wxTeXFile(const wxTeXFile&);
+    wxTeXFile& operator=(const wxTeXFile&);
+
+    wxString m_text;
+};
+
+// helper class which manages the classes and function names to ignore for
+// the documentation purposes (used by both HelpGenVisitor and DocManager)
+class IgnoreNamesHandler
+{
+public:
+    IgnoreNamesHandler() : m_ignore(CompareIgnoreListEntries) { }
+    ~IgnoreNamesHandler() { WX_CLEAR_ARRAY(m_ignore); }
+
+    // load file with classes/functions to ignore (add them to the names we
+    // already have)
+    bool AddNamesFromFile(const wxString& filename);
+
+    // return TRUE if we ignore this function
+    bool IgnoreMethod(const wxString& classname,
+                      const wxString& funcname) const
+    {
+        if ( IgnoreClass(classname) )
+            return TRUE;
+
+        IgnoreListEntry ignore(classname, funcname);
+
+        return m_ignore.Index(&ignore) != wxNOT_FOUND;
+    }
+
+    // return TRUE if we ignore this class entirely
+    bool IgnoreClass(const wxString& classname) const
+    {
+        IgnoreListEntry ignore(classname, "");
+
+        return m_ignore.Index(&ignore) != wxNOT_FOUND;
     }
+
+protected:
+    struct IgnoreListEntry
+    {
+        IgnoreListEntry(const wxString& classname,
+                        const wxString& funcname)
+            : m_classname(classname), m_funcname(funcname)
+        {
+        }
+
+        wxString m_classname;
+        wxString m_funcname;    // if empty, ignore class entirely
+    };
+
+    static int CompareIgnoreListEntries(IgnoreListEntry *first,
+                                        IgnoreListEntry *second);
+
+    // for efficiency, let's sort it
+    WX_DEFINE_SORTED_ARRAY(IgnoreListEntry *, ArrayNamesToIgnore);
+
+    ArrayNamesToIgnore m_ignore;
+
+private:
+    IgnoreNamesHandler(const IgnoreNamesHandler&);
+    IgnoreNamesHandler& operator=(const IgnoreNamesHandler&);
 };
 
+// visitor implementation which writes all collected data to a .tex file
 class HelpGenVisitor : public spVisitor
 {
 public:
     // ctor
-    HelpGenVisitor();
+    HelpGenVisitor(const wxString& directoryOut, bool overwrite);
 
     virtual void VisitFile( spFile& fl );
     virtual void VisitClass( spClass& cl );
     virtual void VisitEnumeration( spEnumeration& en );
     virtual void VisitTypeDef( spTypeDef& td );
+    virtual void VisitPreprocessorLine( spPreprocessorLine& pd );
     virtual void VisitAttribute( spAttribute& attr );
     virtual void VisitOperation( spOperation& op );
     virtual void VisitParameter( spParameter& param );
 
     void EndVisit();
 
+    // get our `ignore' object
+    IgnoreNamesHandler& GetIgnoreHandler() { return m_ignoreNames; }
+
     // shut up g++ warning (ain't it stupid?)
     virtual ~HelpGenVisitor() { }
 
@@ -108,79 +326,513 @@ protected:
     // write the headers for corresponding sections (only once)
     void InsertDataStructuresHeader();
     void InsertMethodsHeader();
-    
+
     // terminate the function documentation if it was started
     void CloseFunction();
 
-    wxTeXFile m_file;  // file we're writing to now
+    // write out all function docs when there are no more left in this class
+    // after sorting them in alphabetical order
+    void CloseClass();
+
+    wxString  m_directoryOut,   // directory for the output
+              m_fileHeader;     // name of the .h file we parse
+    bool      m_overwrite;      // overwrite existing files?
+    wxTeXFile m_file;           // file we're writing to now
 
     // state variables
     bool m_inClass,         // TRUE after file successfully opened
          m_inTypesSection,  // enums & typedefs go there
          m_inMethodSection, // functions go here
-         m_isFirstParam,    // first parameter of current function?
-         m_inFunction;      // we're parsing a function declaration
+         m_isFirstParam;    // first parameter of current function?
+
+    // non empty while parsing a class
+    wxString m_classname;
+
+    // these are only non-empty while parsing a method:
+    wxString m_funcName,    // the function name
+             m_textFunc;    // the function doc text
+
+    // the array containing the documentation entries for the functions in the
+    // class currently being parsed
+    FunctionDocEntries m_arrayFuncDocs;
 
     // holders for "saved" documentation
-    wxString m_textStoredEnums,
-             m_textStoredTypedefs,
+    wxString m_textStoredTypedefs,
              m_textStoredFunctionComment;
+
+    // for enums we have to use an array as we can't intermix the normal text
+    // and the text inside verbatim environment
+    wxArrayString m_storedEnums,
+                  m_storedEnumsVerb;
+
+    // headers included by this file
+    wxArrayString m_headers;
+
+    // ignore handler: tells us which classes to ignore for doc generation
+    // purposes
+    IgnoreNamesHandler m_ignoreNames;
+
+private:
+    HelpGenVisitor(const HelpGenVisitor&);
+    HelpGenVisitor& operator=(const HelpGenVisitor&);
 };
 
-// -----------------------------------------------------------------------------
-// private functions
-// -----------------------------------------------------------------------------
+// documentation manager - a class which parses TeX files and remembers the
+// functions documented in them and can later compare them with all functions
+// found under ctxTop by C++ parser
+class DocManager
+{
+public:
+    DocManager(bool checkParamNames);
+    ~DocManager();
+
+    // returns FALSE on failure
+    bool ParseTeXFile(const wxString& filename);
+
+    // returns FALSE if there were any differences
+    bool DumpDifferences(spContext *ctxTop) const;
+
+    // get our `ignore' object
+    IgnoreNamesHandler& GetIgnoreHandler() { return m_ignoreNames; }
+
+protected:
+    // parsing TeX files
+    // -----------------
+
+    // returns the length of 'match' if the string 'str' starts with it or 0
+    // otherwise
+    static size_t TryMatch(const char *str, const char *match);
+
+    // skip spaces: returns pointer to first non space character (also
+    // updates the value of m_line)
+    const char *SkipSpaces(const char *p)
+    {
+        while ( isspace(*p) ) {
+            if ( *p++ == '\n' )
+                m_line++;
+        }
+
+        return p;
+    }
+
+    // skips characters until the next 'c' in '*pp' unless it ends before in
+    // which case FALSE is returned and pp points to '\0', otherwise TRUE is
+    // returned and pp points to 'c'
+    bool SkipUntil(const char **pp, char c);
+
+    // the same as SkipUntil() but only spaces are skipped: on first non space
+    // character different from 'c' the function stops and returns FALSE
+    bool SkipSpaceUntil(const char **pp, char c);
+
+    // extract the string between {} and modify '*pp' to point at the
+    // character immediately after the closing '}'. The returned string is empty
+    // on error.
+    wxString ExtractStringBetweenBraces(const char **pp);
+
+    // the current file and line while we're in ParseTeXFile (for error
+    // messages)
+    wxString m_filename;
+    size_t   m_line;
+
+    // functions and classes to ignore during diff
+    // -------------------------------------------
+
+    IgnoreNamesHandler m_ignoreNames;
+
+    // information about all functions documented in the TeX file(s)
+    // -------------------------------------------------------------
+
+    // info about a type: for now stored as text string, but must be parsed
+    // further later (to know that "char *" == "char []" - TODO)
+    class TypeInfo
+    {
+    public:
+        TypeInfo(const wxString& type) : m_type(type) { }
+
+        bool operator==(const wxString& type) const { return m_type == type; }
+        bool operator!=(const wxString& type) const { return m_type != type; }
+
+        const wxString& GetName() const { return m_type; }
+
+    private:
+        wxString m_type;
+    };
+
+    // info abotu a function parameter
+    class ParamInfo
+    {
+    public:
+        ParamInfo(const wxString& type,
+                  const wxString& name,
+                  const wxString& value)
+            : m_type(type), m_name(name), m_value(value)
+        {
+        }
+
+        const TypeInfo& GetType() const { return m_type; }
+        const wxString& GetName() const { return m_name; }
+        const wxString& GetDefValue() const { return m_value; }
+
+    private:
+        TypeInfo m_type;      // type of parameter
+        wxString m_name;      // name
+        wxString m_value;     // default value
+    };
+
+    WX_DEFINE_ARRAY(ParamInfo *, ArrayParamInfo);
+
+    // info about a function
+    struct MethodInfo
+    {
+    public:
+        enum MethodFlags
+        {
+            Const   = 0x0001,
+            Virtual = 0x0002,
+            Pure    = 0x0004,
+            Static  = 0x0008,
+            Vararg  = 0x0010
+        };
+
+        MethodInfo(const wxString& type,
+                   const wxString& name,
+                   const ArrayParamInfo& params)
+            : m_typeRet(type), m_name(name), m_params(params)
+        {
+            m_flags = 0;
+        }
+
+        void SetFlag(MethodFlags flag) { m_flags |= flag; }
+
+        const TypeInfo& GetType() const { return m_typeRet; }
+        const wxString& GetName() const { return m_name; }
+        const ParamInfo& GetParam(size_t n) const { return *(m_params[n]); }
+        size_t GetParamCount() const { return m_params.GetCount(); }
+
+        bool HasFlag(MethodFlags flag) const { return (m_flags & flag) != 0; }
+
+        ~MethodInfo() { WX_CLEAR_ARRAY(m_params); }
+
+    private:
+        TypeInfo m_typeRet;     // return type
+        wxString m_name;
+        int      m_flags;       // bit mask of the value from the enum above
+
+        ArrayParamInfo m_params;
+    };
+
+    WX_DEFINE_ARRAY(MethodInfo *, ArrayMethodInfo);
+    WX_DEFINE_ARRAY(ArrayMethodInfo *, ArrayMethodInfos);
+
+    // first array contains the names of all classes we found, the second has a
+    // pointer to the array of methods of the given class at the same index as
+    // the class name appears in m_classes
+    wxArrayString    m_classes;
+    ArrayMethodInfos m_methods;
+
+    // are we checking parameter names?
+    bool m_checkParamNames;
+
+private:
+    DocManager(const DocManager&);
+    DocManager& operator=(const DocManager&);
+};
 
 // =============================================================================
 // implementation
 // =============================================================================
 
-int main(int argc, char **argv)
+// this function never returns
+static void usage()
 {
-    if ( argc < 2 ) {
-        wxLogError("usage: %s <header files...>\n", argv[0]);
+    wxString prog = wxTheApp->argv[0];
+    wxString basename = prog.AfterLast('/');
+#ifdef __WXMSW__
+    if ( !basename )
+        basename = prog.AfterLast('\\');
+#endif
+    if ( !basename )
+        basename = prog;
+
+    wxLogMessage(
+"usage: %s [global options] <mode> [mode options] <files...>\n"
+"\n"
+"   where global options are:\n"
+"       -q          be quiet\n"
+"       -v          be verbose\n"
+"       -H          give this usage message\n"
+"       -V          print the version info\n"
+"       -i file     file with classes/function to ignore\n"
+"\n"
+"   where mode is one of: dump, diff\n"
+"\n"
+"   dump means generate .tex files for TeX2RTF converter from specified\n"
+"   headers files, mode options are:\n"
+"       -f          overwrite existing files\n"
+"       -o outdir   directory for generated files\n"
+"\n"
+"   diff means compare the set of methods documented .tex file with the\n"
+"   methods declared in the header:\n"
+"           %s diff <file.h> <files.tex...>.\n"
+"   mode specific options are:\n"
+"       -p          do check parameter names (not done by default)\n"
+"\n", basename.c_str(), basename.c_str());
+
+    exit(1);
+}
+
+int HelpGenApp::OnRun()
+{
+    enum
+    {
+        Mode_None,
+        Mode_Dump,
+        Mode_Diff
+    } mode = Mode_None;
 
-        return 1;
+    if ( argc < 2 ) {
+        usage();
     }
 
-    // be verbose
-    wxLog::GetActiveTarget()->SetVerbose();
+    wxArrayString filesH, filesTeX;
+    wxString directoryOut,      // directory for 'dmup' output
+             ignoreFile;        // file with classes/functions to ignore
+    bool overwrite = FALSE,     // overwrite existing files during 'dump'?
+         paramNames = FALSE;    // check param names during 'diff'?
+
+    for ( int current = 1; current < argc ; current++ ) {
+        // all options have one letter
+        if ( argv[current][0] == '-' ) {
+            if ( argv[current][2] == '\0' ) {
+                switch ( argv[current][1] ) {
+                    case 'v':
+                        // be verbose
+                        wxLog::GetActiveTarget()->SetVerbose();
+                        continue;
+
+                    case 'q':
+                        // be quiet
+                        wxLog::GetActiveTarget()->SetVerbose(FALSE);
+                        continue;
+
+                    case 'H':
+                        // help requested
+                        usage();
+                        // doesn't return
+
+                    case 'V':
+                        // version requested
+                        wxLogMessage("HelpGen version %s\n"
+                                     "(c) 1999-2001 Vadim Zeitlin\n",
+                                     GetVersionString().c_str());
+                        return 0;
+
+                    case 'i':
+                        current++;
+                        if ( current >= argc ) {
+                            wxLogError("-i option requires an argument.");
+
+                            break;
+                        }
+
+                        ignoreFile = argv[current];
+                        continue;
+
+                    case 'p':
+                        if ( mode != Mode_Diff ) {
+                            wxLogError("-p is only valid with diff.");
+
+                            break;
+                        }
+
+                        paramNames = TRUE;
+                        continue;
+
+                    case 'f':
+                        if ( mode != Mode_Dump ) {
+                            wxLogError("-f is only valid with dump.");
+
+                            break;
+                        }
+
+                        overwrite = TRUE;
+                        continue;
+
+                    case 'o':
+                        if ( mode != Mode_Dump ) {
+                            wxLogError("-o is only valid with dump.");
+
+                            break;
+                        }
+
+                        current++;
+                        if ( current >= argc ) {
+                            wxLogError("-o option requires an argument.");
+
+                            break;
+                        }
+
+                        directoryOut = argv[current];
+                        if ( !!directoryOut ) {
+                            // terminate with a '/' if it doesn't have it
+                            switch ( directoryOut.Last() ) {
+                                case '/':
+#ifdef __WXMSW__
+                                case '\\':
+#endif
+                                    break;
+
+                                default:
+                                    directoryOut += '/';
+                            }
+                        }
+                        //else: it's empty, do nothing
+
+                        continue;
+
+                    default:
+                        wxLogError("unknown option '%s'", argv[current]);
+                        break;
+                }
+            }
+            else {
+                wxLogError("only one letter options are allowed, not '%s'.",
+                           argv[current]);
+            }
+
+            // only get here after a break from switch or from else branch of if
+
+            usage();
+        }
+        else {
+            if ( mode == Mode_None ) {
+                if ( strcmp(argv[current], "diff") == 0 )
+                    mode = Mode_Diff;
+                else if ( strcmp(argv[current], "dump") == 0 )
+                    mode = Mode_Dump;
+                else {
+                    wxLogError("unknown mode '%s'.", argv[current]);
+
+                    usage();
+                }
+            }
+            else {
+                if ( mode == Mode_Dump || filesH.IsEmpty() ) {
+                    filesH.Add(argv[current]);
+                }
+                else {
+                    // 2nd files and further are TeX files in diff mode
+                    wxASSERT( mode == Mode_Diff );
+
+                    filesTeX.Add(argv[current]);
+                }
+            }
+        }
+    }
 
     // create a parser object and a visitor derivation
     CJSourceParser parser;
-    HelpGenVisitor visitor;
+    HelpGenVisitor visitor(directoryOut, overwrite);
+    if ( !!ignoreFile && mode == Mode_Dump )
+        visitor.GetIgnoreHandler().AddNamesFromFile(ignoreFile);
+
+    spContext *ctxTop = NULL;
 
-    // parse all files
-    for ( int i = 1; i < argc; i++ ) {
-        spContext *ctxTop = parser.ParseFile(argv[i]);
+    // parse all header files
+    size_t nFiles = filesH.GetCount();
+    for ( size_t n = 0; n < nFiles; n++ ) {
+        wxString header = filesH[n];
+        ctxTop = parser.ParseFile(header);
         if ( !ctxTop ) {
-            wxLogWarning("File '%s' couldn't be processed.", argv[i]);
+            wxLogWarning("Header file '%s' couldn't be processed.",
+                         header.c_str());
         }
-        else {
-            ((spFile *)ctxTop)->mFileName = argv[i];
+        else if ( mode == Mode_Dump ) {
+            ((spFile *)ctxTop)->mFileName = header;
             visitor.VisitAll(*ctxTop);
             visitor.EndVisit();
         }
+
+#ifdef __WXDEBUG__
+        if ( 0 && ctxTop )
+            ctxTop->Dump("");
+#endif // __WXDEBUG__
+    }
+
+    // parse all TeX files
+    if ( mode == Mode_Diff ) {
+        if ( !ctxTop ) {
+            wxLogError("Can't complete diff.");
+
+            // failure
+            return FALSE;
+        }
+
+        DocManager docman(paramNames);
+
+        size_t nFiles = filesTeX.GetCount();
+        for ( size_t n = 0; n < nFiles; n++ ) {
+            wxString file = filesTeX[n];
+            if ( !docman.ParseTeXFile(file) ) {
+                wxLogWarning("TeX file '%s' couldn't be processed.",
+                             file.c_str());
+            }
+        }
+
+        if ( !!ignoreFile )
+            docman.GetIgnoreHandler().AddNamesFromFile(ignoreFile);
+
+        docman.DumpDifferences(ctxTop);
     }
 
     return 0;
 }
 
+int main(int argc, char **argv)
+{
+    wxInitializer initializer;
+    if ( !initializer )
+    {
+        fprintf(stderr, "Failed to initialize the wxWindows library, aborting.");
+
+        return -1;
+    }
+       HelpGenApp app;
+       app.argc = argc;
+       app.argv = argv;
+       return app.OnRun();
+}
+
 // -----------------------------------------------------------------------------
 // HelpGenVisitor implementation
 // -----------------------------------------------------------------------------
 
-HelpGenVisitor::HelpGenVisitor()
+HelpGenVisitor::HelpGenVisitor(const wxString& directoryOut,
+                               bool overwrite)
+              : m_directoryOut(directoryOut)
 {
+    m_overwrite = overwrite;
+
     Reset();
 }
 
 void HelpGenVisitor::Reset()
 {
     m_inClass =
-    m_inFunction =
     m_inTypesSection =
-    m_inMethodSection = false;
+    m_inMethodSection = FALSE;
+
+    m_classname =
+    m_funcName =
+    m_textFunc =
+    m_textStoredTypedefs =
+    m_textStoredFunctionComment = "";
+
+    m_arrayFuncDocs.Empty();
+
+    m_storedEnums.Empty();
+    m_storedEnumsVerb.Empty();
+    m_headers.Empty();
 }
 
 void HelpGenVisitor::InsertTypedefDocs()
@@ -191,71 +843,170 @@ void HelpGenVisitor::InsertTypedefDocs()
 
 void HelpGenVisitor::InsertEnumDocs()
 {
-    m_file.WriteTeX(m_textStoredEnums);
-    m_textStoredEnums.Empty();
+    size_t count = m_storedEnums.GetCount();
+    for ( size_t n = 0; n < count; n++ )
+    {
+        m_file.WriteTeX(m_storedEnums[n]);
+        m_file.WriteVerbatim(m_storedEnumsVerb[n] + '\n');
+    }
+
+    m_storedEnums.Empty();
+    m_storedEnumsVerb.Empty();
 }
 
 void HelpGenVisitor::InsertDataStructuresHeader()
 {
     if ( !m_inTypesSection ) {
-        m_inTypesSection = true;
+        m_inTypesSection = TRUE;
 
-        m_file.WriteTeX("\\wxheading{Data structures}\n\n");
+        m_file.WriteVerbatim("\\wxheading{Data structures}\n\n");
     }
 }
 
 void HelpGenVisitor::InsertMethodsHeader()
 {
     if ( !m_inMethodSection ) {
-        m_inMethodSection = true;
+        m_inMethodSection = TRUE;
 
-        m_file.WriteTeX( "\\latexignore{\\rtfignore{\\wxheading{Members}}}\n\n");
+        m_file.WriteVerbatim( "\\latexignore{\\rtfignore{\\wxheading{Members}}}\n\n");
     }
 }
 
 void HelpGenVisitor::CloseFunction()
 {
-    if ( m_inFunction ) {
-        m_inFunction = false;
-
-        wxString totalText;
+    if ( !m_funcName.empty() ) {
         if ( m_isFirstParam ) {
             // no params found
-            totalText << "\\void";
+            m_textFunc << "\\void";
         }
 
-        totalText << "}\n\n";
+        m_textFunc << "}\n\n";
+
+        if ( !m_textStoredFunctionComment.IsEmpty() ) {
+            m_textFunc << m_textStoredFunctionComment << '\n';
+        }
+
+        m_arrayFuncDocs.Add(new FunctionDocEntry(m_funcName, m_textFunc));
+
+        m_funcName.clear();
+    }
+}
+
+void HelpGenVisitor::CloseClass()
+{
+       CloseFunction();
+
+    if ( m_inClass ) {
+        size_t count = m_arrayFuncDocs.GetCount();
+        if ( count ) {
+                       size_t n;
+            FunctionDocEntry::classname = m_classname;
+
+            m_arrayFuncDocs.Sort(FunctionDocEntry::Compare);
+
+                       // Now examine each first line and if it's been seen, cut it
+                       // off (it's a duplicate \membersection)
+                       wxHashTable membersections(wxKEY_STRING);
+
+            for ( n = 0; n < count; n++ )
+                       {
+                wxString section(m_arrayFuncDocs[n].text);
+
+                               // Strip leading whitespace
+                               int pos = section.Find("\\membersection");
+                               if (pos > -1)
+                               {
+                                       section = section.Mid(pos);
+                               }
+
+                               wxString ms(section.BeforeFirst(wxT('\n')));
+                               if (membersections.Get(ms))
+                               {
+                                       m_arrayFuncDocs[n].text = section.AfterFirst(wxT('\n'));
+                               }
+                               else
+                               {
+                                       membersections.Put(ms, & membersections);
+                               }
+            }
+
+            for ( n = 0; n < count; n++ ) {
+                m_file.WriteTeX(m_arrayFuncDocs[n].text);
+            }
 
-        if ( !m_textStoredFunctionComment.IsEmpty() )
-            totalText << m_textStoredFunctionComment << '\n';
+            m_arrayFuncDocs.Empty();
+        }
 
-        m_file.WriteTeX(totalText);
+        m_inClass = FALSE;
+        m_classname.clear();
     }
+       m_file.FlushAll();
 }
 
 void HelpGenVisitor::EndVisit()
 {
     CloseFunction();
+
+    CloseClass();
+
+    m_fileHeader.Empty();
+
+    m_file.FlushAll();
+       if (m_file.IsOpened())
+       {
+               m_file.Flush();
+               m_file.Close();
+       }
+
+    wxLogVerbose("%s: finished generating for the current file.",
+                 GetCurrentTime("%H:%M:%S"));
 }
 
 void HelpGenVisitor::VisitFile( spFile& file )
 {
-    wxLogInfo("Parsing classes from file '%s'...", file.mFileName.c_str());
+    m_fileHeader = file.mFileName;
+    wxLogVerbose("%s: started generating docs for classes from file '%s'...",
+                 GetCurrentTime("%H:%M:%S"), m_fileHeader.c_str());
 }
 
 void HelpGenVisitor::VisitClass( spClass& cl )
 {
+    CloseClass();
+
+       if (m_file.IsOpened())
+       {
+               m_file.Flush();
+               m_file.Close();
+       }
+
     wxString name = cl.GetName();
 
+    if ( m_ignoreNames.IgnoreClass(name) ) {
+        wxLogVerbose("Skipping ignored class '%s'.", name.c_str());
+
+        return;
+    }
+
     // the file name is built from the class name by removing the leading "wx"
     // if any and converting it to the lower case
-    wxString filename = name;
-    if ( filename(0, 2) == "wx" ) {
-        filename.erase(0, 2);
+    wxString filename;
+    if ( name(0, 2) == "wx" ) {
+        filename << name.c_str() + 2;
+    }
+    else {
+        filename << name;
     }
 
     filename.MakeLower();
     filename += ".tex";
+    filename.Prepend(m_directoryOut);
+
+    if ( !m_overwrite && wxFile::Exists(filename) ) {
+        wxLogError("Won't overwrite existing file '%s' - please use '-f'.",
+                   filename.c_str());
+
+        return;
+    }
 
     m_inClass = m_file.Open(filename, wxFile::write);
     if ( !m_inClass ) {
@@ -266,37 +1017,98 @@ void HelpGenVisitor::VisitClass( spClass& cl )
     }
 
     m_inMethodSection =
-    m_inTypesSection = false;
+    m_inTypesSection = FALSE;
 
     wxLogInfo("Created new file '%s' for class '%s'.",
               filename.c_str(), name.c_str());
 
+    // write out the header
+    wxString header;
+    header.Printf("%%\n"
+                  "%% automatically generated by HelpGen %s from\n"
+                  "%% %s at %s\n"
+                  "%%\n"
+                  "\n"
+                  "\n"
+                  "\\section{\\class{%s}}\\label{%s}\n\n",
+                  GetVersionString().c_str(),
+                  m_fileHeader.c_str(),
+                  GetCurrentTime("%d/%b/%y %H:%M:%S"),
+                  name.c_str(),
+                  wxString(name).MakeLower().c_str());
+
+    m_file.WriteVerbatim(header);
+
     // the entire text we're writing to file
     wxString totalText;
 
-    // write out the header
-    {
-        time_t timeNow = time(NULL);
-        wxString header;
-        header.Printf("% automatically generated by HelpGen from %s at "
-                      "%s" // no '\n' here because ctime() inserts one
-                      "\\section{\\class{%s}}\\label{%s}\n",
-                      filename.c_str(), ctime(&timeNow),
-                      name.c_str(), wxString(name).MakeLower().c_str());
+    // if the header includes other headers they must be related to it... try to
+    // automatically generate the "See also" clause
+    if ( !m_headers.IsEmpty() ) {
+        // correspondence between wxWindows headers and class names
+        static const char *headers[] = {
+            "object",
+            "defs",
+            "string",
+            "dynarray",
+            "file",
+            "time",
+        };
+
+        // NULL here means not to insert anything in "See also" for the
+        // corresponding header
+        static const char *classes[] = {
+            NULL,
+            NULL,
+            NULL,
+            NULL,
+            "wxFile",
+            "wxTime",
+        };
+
+        wxASSERT_MSG( WXSIZEOF(headers) == WXSIZEOF(classes),
+                      "arrays must be in sync!" );
+
+        wxArrayInt interestingClasses;
+
+        size_t count = m_headers.Count(), index;
+        for ( size_t n = 0; n < count; n++ ) {
+            wxString baseHeaderName = m_headers[n].Before('.');
+            if ( baseHeaderName(0, 3) != "wx/" )
+                continue;
+
+            baseHeaderName.erase(0, 3);
+            for ( index = 0; index < WXSIZEOF(headers); index++ ) {
+                if ( Stricmp(baseHeaderName, headers[index]) == 0 )
+                    break;
+            }
+
+            if ( (index < WXSIZEOF(headers)) && classes[index] ) {
+                // interesting header
+                interestingClasses.Add(index);
+            }
+        }
 
-        totalText << header << '\n';
+        if ( !interestingClasses.IsEmpty() ) {
+            // do generate "See also" clause
+            totalText << "\\wxheading{See also:}\n\n";
+
+            count = interestingClasses.Count();
+            for ( index = 0; index < count; index++ ) {
+                if ( index > 0 )
+                    totalText << ", ";
+
+                totalText << MakeHelpref(classes[interestingClasses[index]]);
+            }
+
+            totalText << "\n\n";
+        }
     }
 
     // the comment before the class generally explains what is it for so put it
     // in place of the class description
     if ( cl.HasComments() ) {
-        wxString comment;
-        const MCommentListT& comments = cl.GetCommentList();
-        for ( MCommentListT::const_iterator i = comments.begin();
-              i != comments.end();
-              i++ ) {
-            comment << (*i)->GetText();
-        }
+        wxString comment = GetAllComments(cl);
 
         totalText << '\n' << comment << '\n';
     }
@@ -309,7 +1121,7 @@ void HelpGenVisitor::VisitClass( spClass& cl )
         derived << "No base class";
     }
     else {
-        bool first = true;
+        bool first = TRUE;
         for ( StrListT::const_iterator i = baseClasses.begin();
               i != baseClasses.end();
               i++ ) {
@@ -318,16 +1130,22 @@ void HelpGenVisitor::VisitClass( spClass& cl )
                 derived << "\\\\\n";
             }
             else {
-                first = false;
+                first = FALSE;
             }
 
             wxString baseclass = *i;
-            derived << "\\helpref{" << baseclass << "}"
-                       "{ " << baseclass.MakeLower()  << "}";
+            derived << "\\helpref{" << baseclass << "}";
+            derived << "{" << baseclass.MakeLower()  << "}";
         }
     }
     totalText << derived << "\n\n";
 
+    // include file section
+    wxString includeFile = "\\wxheading{Include files}\n\n";
+    includeFile << "<" << m_fileHeader << ">";
+
+    totalText << includeFile << "\n\n";
+
     // write all this to file
     m_file.WriteTeX(totalText);
 
@@ -335,6 +1153,8 @@ void HelpGenVisitor::VisitClass( spClass& cl )
     InsertDataStructuresHeader();
     InsertTypedefDocs();
     InsertEnumDocs();
+
+       //m_file.Flush();
 }
 
 void HelpGenVisitor::VisitEnumeration( spEnumeration& en )
@@ -351,25 +1171,25 @@ void HelpGenVisitor::VisitEnumeration( spEnumeration& en )
     }
 
     // simply copy the enum text in the docs
-    wxString enumeration;
-    enumeration << "{\\small \\begin{verbatim}\n"
-                << en.mEnumContent
-                << "\n\\end{verbatim}}\n";
+    wxString enumeration = GetAllComments(en),
+             enumerationVerb;
+
+    enumerationVerb << "\\begin{verbatim}\n"
+                    << en.mEnumContent
+                    << "\n\\end{verbatim}\n";
 
     // remember for later use if we're not inside a class yet
     if ( !m_inClass ) {
-        if ( !m_textStoredEnums.IsEmpty() ) {
-            m_textStoredEnums << '\n';
-        }
-
-        m_textStoredEnums << enumeration;
+        m_storedEnums.Add(enumeration);
+        m_storedEnumsVerb.Add(enumerationVerb);
     }
     else {
         // write the header for this section if not done yet
         InsertDataStructuresHeader();
 
-        enumeration << '\n';
         m_file.WriteTeX(enumeration);
+        m_file.WriteVerbatim(enumerationVerb);
+        m_file.WriteVerbatim('\n');
     }
 }
 
@@ -377,7 +1197,47 @@ void HelpGenVisitor::VisitTypeDef( spTypeDef& td )
 {
     CloseFunction();
 
-    wxFAIL_MSG("don't know how to document typedefs yet");
+    if ( m_inMethodSection ) {
+        // FIXME that's a bug, but tell the user aboit it nevertheless...
+        wxLogWarning("typedef '%s' ignored, please put it before the class "
+                     "methods.", td.GetName().c_str());
+        return;
+    }
+
+    wxString typedefdoc;
+    typedefdoc << "{\\small \\begin{verbatim}\n"
+               << "typedef " << td.mOriginalType << ' ' << td.GetName()
+               << "\n\\end{verbatim}}\n"
+               << GetAllComments(td);
+
+    // remember for later use if we're not inside a class yet
+    if ( !m_inClass ) {
+        if ( !m_textStoredTypedefs.IsEmpty() ) {
+            m_textStoredTypedefs << '\n';
+        }
+
+        m_textStoredTypedefs << typedefdoc;
+    }
+    else {
+        // write the header for this section if not done yet
+        InsertDataStructuresHeader();
+
+        typedefdoc << '\n';
+        m_file.WriteTeX(typedefdoc);
+    }
+}
+
+void HelpGenVisitor::VisitPreprocessorLine( spPreprocessorLine& pd )
+{
+    switch ( pd.GetStatementType() ) {
+        case SP_PREP_DEF_INCLUDE_FILE:
+            m_headers.Add(pd.CPP_GetIncludedFileNeme());
+            break;
+
+        case SP_PREP_DEF_DEFINE_SYMBOL:
+            // TODO decide if it's a constant and document it if it is
+            break;
+    }
 }
 
 void HelpGenVisitor::VisitAttribute( spAttribute& attr )
@@ -388,15 +1248,21 @@ void HelpGenVisitor::VisitAttribute( spAttribute& attr )
     if ( !m_inClass || !attr.IsPublic() )
         return;
 
-    wxFAIL_MSG("don't know how to document member vars yet");
+    wxLogWarning("Ignoring member variable '%s'.", attr.GetName().c_str());
 }
 
 void HelpGenVisitor::VisitOperation( spOperation& op )
 {
     CloseFunction();
 
-    if ( !m_inClass || !op.IsInClass() ) {
-        // FIXME that's a bug too
+    if ( !m_inClass ) {
+        // we don't generate docs right now - either we ignore this class
+        // entirely or we couldn't open the file
+        return;
+    }
+
+    if ( !op.IsInClass() ) {
+        // TODO document global functions
         wxLogWarning("skipped global function '%s'.", op.GetName().c_str());
 
         return;
@@ -407,65 +1273,779 @@ void HelpGenVisitor::VisitOperation( spOperation& op )
         return;
     }
 
+    m_classname = op.GetClass().GetName();
+    wxString funcname = op.GetName();
+
+    if ( m_ignoreNames.IgnoreMethod(m_classname, funcname) ) {
+        wxLogVerbose("Skipping ignored '%s::%s'.",
+                     m_classname.c_str(), funcname.c_str());
+
+        return;
+    }
+
     InsertMethodsHeader();
 
     // save state info
-    m_inFunction =
-    m_isFirstParam = true;
+    m_funcName = funcname;
+    m_isFirstParam = TRUE;
 
-    m_textStoredFunctionComment.Empty();
-    const MCommentListT& comments = op.GetCommentList();
-    for ( MCommentListT::const_iterator i = comments.begin();
-          i != comments.end();
-          i++ ) {
-        m_textStoredFunctionComment << (*i)->GetText();
-    }
+    m_textStoredFunctionComment = GetAllComments(op);
 
     // start function documentation
     wxString totalText;
-    const char *funcname = op.GetName().c_str();
-    const char *classname = op.GetClass().GetName().c_str();
-               
+
     // check for the special case of dtor
     wxString dtor;
-    if ( (funcname[0] == '~') && (strcmp(funcname + 1, classname) == 0) ) {
-        dtor.Printf("\\destruct{%s}", classname);
+    if ( (funcname[0] == '~') && (m_classname == funcname.c_str() + 1) ) {
+        dtor.Printf("\\destruct{%s}", m_classname.c_str());
         funcname = dtor;
     }
 
-    totalText.Printf("\\membersection{%s::%s}\\label{%s}\n"
-                     "\\%sfunc{%s}{%s}{",
-                     classname, funcname,
-                     MakeLabel(classname, funcname).c_str(),
-                     op.mIsConstant ? "const" : "",
-                     op.mRetType.c_str(),
-                     funcname);
-
-    m_file.WriteTeX(totalText);
+       m_textFunc.Printf("\n"
+               "\\membersection{%s::%s}\\label{%s}\n",
+               m_classname.c_str(), funcname.c_str(),
+               MakeLabel(m_classname, funcname).c_str());
+
+       wxString func;
+       func.Printf("\n"
+                      "\\%sfunc{%s%s}{%s}{",
+                      op.mIsConstant ? "const" : "",
+                      op.mIsVirtual ? "virtual " : "",
+                      op.mRetType.c_str(),
+                      funcname.c_str());
+       m_textFunc += func;
 }
 
 void HelpGenVisitor::VisitParameter( spParameter& param )
 {
-    if ( !m_inFunction )
+    if ( m_funcName.empty() )
         return;
 
-    wxString totalText;
     if ( m_isFirstParam ) {
-        m_isFirstParam = false;
+        m_isFirstParam = FALSE;
     }
     else {
-        totalText << ", ";
+        m_textFunc << ", ";
     }
-    
-    totalText << "\\param{" << param.mType << " }{" << param.GetName();
+
+    m_textFunc << "\\param{" << param.mType << " }{" << param.GetName();
     wxString defvalue = param.mInitVal;
     if ( !defvalue.IsEmpty() ) {
-        totalText << " = " << defvalue;
+        m_textFunc << " = " << defvalue;
     }
-    
-    totalText << '}';
 
-    m_file.WriteTeX(totalText);
+    m_textFunc << '}';
+}
+
+// ---------------------------------------------------------------------------
+// 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(&current, '\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(&current, '{') ) {
+            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(&current, ':') || *(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(&current, '}') ) {
+            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(&current, '{') ) { 
+            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(&current, '}') ) {
+                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(&current, '{') ||
+             (current++, !SkipSpaceUntil(&current, '\\')) ) {
+            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(&current);
+                if ( !!paramType ) {
+                    wxString paramText = ExtractStringBetweenBraces(&current);
+                    if ( !!paramText ) {
+                        // the param declaration may contain default value
+                        wxString paramName = paramText.BeforeFirst('='),
+                                 paramValue = paramText.AfterFirst('=');
+
+                        // sanitize all strings
+                        TeXUnfilter(&paramValue);
+                        TeXUnfilter(&paramName);
+                        TeXUnfilter(&paramType);
+
+                        paramValues.Add(paramValue);
+                        paramNames.Add(paramName);
+                        paramTypes.Add(paramType);
+                    }
+                }
+                else {
+                    // vararg function?
+                    wxString paramText = ExtractStringBetweenBraces(&current);
+                    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;
 }
 
 // -----------------------------------------------------------------------------
@@ -475,12 +2055,12 @@ void HelpGenVisitor::VisitParameter( spParameter& param )
 static wxString MakeLabel(const char *classname, const char *funcname)
 {
     wxString label(classname);
-    if ( funcname[0] == '\\' ) {
+    if ( funcname && funcname[0] == '\\' ) {
         // we may have some special TeX macro - so far only \destruct exists,
         // but may be later others will be added
         static const char *macros[] = { "destruct" };
         static const char *replacement[] = { "dtor" };
-        
+
         size_t n;
         for ( n = 0; n < WXSIZEOF(macros); n++ ) {
             if ( strncmp(funcname + 1, macros[n], strlen(macros[n])) == 0 ) {
@@ -498,18 +2078,231 @@ static wxString MakeLabel(const char *classname, const char *funcname)
         }
     }
 
-    label << funcname;
+    if ( funcname ) {
+        // special treatment for operatorXXX() stuff because the C operators
+        // are not valid in LaTeX labels
+        wxString oper;
+        if ( wxString(funcname).StartsWith("operator", &oper) ) {
+            label << "operator";
+
+            static const struct
+            {
+                const char *oper;
+                const char *name;
+            } operatorNames[] =
+            {
+                { "=",  "assign" },
+                { "==", "equal" },
+            };
+
+            size_t n;
+            for ( n = 0; n < WXSIZEOF(operatorNames); n++ ) {
+                if ( oper == operatorNames[n].oper ) {
+                    label << operatorNames[n].name;
+
+                    break;
+                }
+            }
+
+            if ( n == WXSIZEOF(operatorNames) ) {
+                wxLogWarning("unknown operator '%s' - making dummy label.",
+                             oper.c_str());
+
+                label << "unknown";
+            }
+        }
+        else // simply use the func name
+        {
+            label << funcname;
+        }
+    }
 
     label.MakeLower();
 
     return label;
 }
 
+static wxString MakeHelpref(const char *argument)
+{
+    wxString helpref;
+    helpref << "\\helpref{" << argument << "}{" << MakeLabel(argument) << '}';
+
+    return helpref;
+}
+
 static void TeXFilter(wxString* str)
+{
+    // TeX special which can be quoted (don't include backslash nor braces as
+    // we generate them 
+    static wxRegEx reNonSpecialSpecials("[#$%&_]"),
+                   reAccents("[~^]");
+
+    // just quote
+    reNonSpecialSpecials.ReplaceAll(str, "\\\\\\0");
+
+    // can't quote these ones as they produce accents when preceded by
+    // backslash, so put them inside verb
+    reAccents.ReplaceAll(str, "\\\\verb|\\0|");
+}
+
+static void TeXUnfilter(wxString* str)
 {
     // FIXME may be done much more quickly
-    str->Replace("&", "\\&");
-    str->Replace("_", "\\_");
+    str->Trim(TRUE);
+    str->Trim(FALSE);
+
+    // undo TeXFilter
+    static wxRegEx reNonSpecialSpecials("\\\\([#$%&_{}])"),
+                   reAccents("\\\\verb|([~^])|");
+
+    reNonSpecialSpecials.ReplaceAll(str, "\\1");
+    reAccents.ReplaceAll(str, "\\1");
+}
+
+static wxString GetAllComments(const spContext& ctx)
+{
+    wxString comments;
+    const MCommentListT& commentsList = ctx.GetCommentList();
+    for ( MCommentListT::const_iterator i = commentsList.begin();
+          i != commentsList.end();
+          i++ ) {
+        wxString comment = (*i)->GetText();
+
+        // don't take comments like "// ----------" &c
+        comment.Trim(FALSE);
+        if ( !!comment &&
+              comment == wxString(comment[0u], comment.length() - 1) + '\n' )
+            comments << "\n";
+        else
+            comments << comment;
+    }
+
+    return comments;
+}
+
+static const char *GetCurrentTime(const char *timeFormat)
+{
+    static char s_timeBuffer[128];
+    time_t timeNow;
+    struct tm *ptmNow;
+
+    time(&timeNow);
+    ptmNow = localtime(&timeNow);
+
+    strftime(s_timeBuffer, WXSIZEOF(s_timeBuffer), timeFormat, ptmNow);
+
+    return s_timeBuffer;
 }
 
+static const wxString GetVersionString()
+{
+    wxString version = "$Revision$";
+    wxRegEx("^\\$Revision$$").ReplaceFirst(&version, "\\1");
+    return version;
+}
+
+/*
+   $Log$
+   Revision 1.22  2002/01/21 21:18:50  JS
+   Now adds 'include file' heading
+
+   Revision 1.21  2002/01/04 11:06:09  JS
+   Fixed missing membersections bug and also bug with functions not being written
+   in the right class
+
+   Revision 1.20  2002/01/03 14:23:33  JS
+   Added code to make it not duplicate membersections for overloaded functions
+
+   Revision 1.19  2002/01/03 13:34:12  JS
+   Added FlushAll to CloseClass, otherwise text was only flushed right at the end,
+   and appeared in one file.
+
+   Revision 1.18  2002/01/03 12:02:47  JS
+   Added main() and corrected VC++ project settings
+
+   Revision 1.17  2001/11/30 21:43:35  VZ
+   now the methods are sorted in the correct order in the generated docs
+
+   Revision 1.16  2001/11/28 19:27:33  VZ
+   HelpGen doesn't work in GUI mode
+
+   Revision 1.15  2001/11/22 21:59:58  GD
+   use "..." instead of <...> for wx headers
+
+   Revision 1.14  2001/07/19 13:51:29  VZ
+   fixes to version string
+
+   Revision 1.13  2001/07/19 13:44:57  VZ
+   1. compilation fixes
+   2. don't quote special characters inside verbatim environment
+
+   Revision 1.12  2000/10/09 13:53:33  juliansmart
+
+   Doc corrections; added HelpGen project files
+
+   Revision 1.11  2000/07/15 19:50:42  cvsuser
+   merged 2.2 branch
+
+   Revision 1.10.2.2  2000/03/27 15:33:10  VZ
+   don't trasnform output dir name to lower case
+
+   Revision 1.10  2000/03/11 10:05:23  VS
+   now compiles with wxBase
+
+   Revision 1.9  2000/01/16 13:25:21  VS
+   compilation fixes (gcc)
+
+   Revision 1.8  1999/09/13 14:29:39  JS
+
+   Made HelpGen into a wxWin app (still uses command-line args); moved includes
+   into src for simplicity; added VC++ 5 project file
+
+   Revision 1.7  1999/02/21 22:32:32  VZ
+   1. more C++ parser fixes - now it almost parses wx/string.h
+    a) #if/#ifdef/#else (very) limited support
+    b) param type fix - now indirection chars are correctly handled
+    c) class/struct/union distinction
+    d) public/private fixes
+    e) Dump() function added - very useful for debugging
+
+   2. option to ignore parameter names during 'diff' (in fact, they're ignored
+      by default, and this option switches it on)
+
+   Revision 1.6  1999/02/20 23:00:26  VZ
+   1. new 'diff' mode which seems to work
+   2. output files are not overwritten in 'dmup' mode
+   3. fixes for better handling of const functions and operators
+    ----------------------------
+    revision 1.5
+    date: 1999/02/15 23:07:25;  author: VZ;  state: Exp;  lines: +106 -45
+    1. Parser improvements
+     a) const and virtual methods are parsed correctly (not static yet)
+     b) "const" which is part of the return type is not swallowed
+
+    2. HelpGen improvements: -o outputdir parameter added to the cmd line,
+       "//---------" kind comments discarded now.
+    ----------------------------
+    revision 1.4
+    date: 1999/01/13 14:23:31;  author: JS;  state: Exp;  lines: +4 -4
+
+    some tweaks to HelpGen
+    ----------------------------
+    revision 1.3
+    date: 1999/01/09 20:18:03;  author: JS;  state: Exp;  lines: +7 -2
+
+    HelpGen starting to compile with VC++
+    ----------------------------
+    revision 1.2
+    date: 1999/01/08 19:46:22;  author: VZ;  state: Exp;  lines: +208 -35
+
+    supports typedefs, generates "See also:" and adds "virtual " for virtual
+    functions
+    ----------------------------
+    revision 1.1
+    date: 1999/01/08 17:45:55;  author: VZ;  state: Exp;
+
+    HelpGen is a prototype of the tool for automatic generation of the .tex files
+    for wxWindows documentation from C++ headers
+*/
+
 /* vi: set tw=80 et ts=4 sw=4: */