1/////////////////////////////////////////////////////////////////////////////
2// Name:        HelpGen.cpp
3// Purpose:     Main program file for HelpGen
4// Author:      Vadim Zeitlin <zeitlin@dptmaths.ens-cachan.fr>
5// Modified by:
6// Created:     06/01/99
7// RCS-ID:      $Id: HelpGen.cpp 34465 2005-05-31 17:47:46Z ABX $
8// Copyright:   (c) 1999 VZ
9// Licence:     wxWindows Licence
10/////////////////////////////////////////////////////////////////////////////
11
12/*
13   BUGS
14
15    1. wx/string.h confuses C++ parser terribly
16    2. C++ parser doesn't know about virtual functions, nor static ones
17    3. param checking is not done for vararg functions
18    4. type comparison is dumb: it doesn't know that "char *" is the same
19       that "char []" nor that "const char *" is the same as "char const *"
20
21   TODO (+ means fixed), see also the change log at the end of the file.
22
23   (i) small fixes in the current version
24
25   +1. Quote special TeX characters like '&' and '_' (=> derive from wxFile)
26    2. Document typedefs
27    3. Document global variables
28    4. Document #defines
29   +5. Program options
30    6. Include file name/line number in the "diff" messages?
31   +7. Support for vararg functions
32
33   (ii) plans for version 2
34    1. Use wxTextFile for direct file access to avoid one scan method problems
35    2. Use command line parser class for the options
36    3. support for overloaded functions in diff mode (search for OVER)
37
38   (iii) plans for version 3
39    1. Merging with existing files
40    2. GUI
41*/
42
43// =============================================================================
44// declarations
45// =============================================================================
46
47// -----------------------------------------------------------------------------
48// headers
49// -----------------------------------------------------------------------------
50
51// wxWidgets
52#include "wx/wxprec.h"
53
54#ifdef __BORLANDC__
55    #pragma hdrstop
56#endif
57
58#if wxUSE_UNICODE
59    #error "HelpGen doesn't build in Unicode mode"
60#endif
61
62#ifndef WX_PRECOMP
63    #include "wx/string.h"
64    #include "wx/log.h"
65    #include "wx/dynarray.h"
66    #include "wx/app.h"
67#endif // WX_PRECOMP
68
69#include "wx/file.h"
70#include "wx/regex.h"
71#include "wx/hash.h"
72
73// C++ parsing classes
74#include "cjparser.h"
75
76// standard headers
77#include <stdio.h>
78#include <time.h>
79
80// -----------------------------------------------------------------------------
81// private functions
82// -----------------------------------------------------------------------------
83
84// return the label for the given function name (i.e. argument of \label)
85static wxString MakeLabel(const wxChar *classname, const wxChar *funcname = NULL);
86
87// return the whole \helpref{arg}{arg_label} string
88static wxString MakeHelpref(const wxChar *argument);
89
90// [un]quote special TeX characters (in place)
91static void TeXFilter(wxString* str);
92static void TeXUnfilter(wxString* str); // also trims spaces
93
94// get all comments associated with this context
95static wxString GetAllComments(const spContext& ctx);
96
97// get the string with current time (returns pointer to static buffer)
98// timeFormat is used for the call of strftime(3)
99static const char *GetCurrentTimeFormatted(const char *timeFormat);
100
101// get the string containing the program version
102static const wxString GetVersionString();
103
104// -----------------------------------------------------------------------------
105// private classes
106// -----------------------------------------------------------------------------
107
108// a function documentation entry
109struct FunctionDocEntry
110{
111    FunctionDocEntry(const wxString& name_, const wxString& text_)
112        : name(name_), text(text_) { }
113
114    // the function name
115    wxString name;
116
117    // the function doc text
118    wxString text;
119
120    // sorting stuff
121    static int Compare(FunctionDocEntry **pp1, FunctionDocEntry **pp2)
122    {
123        // the methods should appear in the following order: ctors, dtor, all
124        // the rest in the alphabetical order
125        bool isCtor1 = (*pp1)->name == classname;
126        bool isCtor2 = (*pp2)->name == classname;
127
128        if ( isCtor1 ) {
129            if ( isCtor2 ) {
130                // we don't order the ctors because we don't know how to do it
131                return 0;
132            }
133
134            // ctor comes before non-ctor
135            return -1;
136        }
137        else {
138            if ( isCtor2 ) {
139                // non-ctor must come after ctor
140                return 1;
141            }
142
143            wxString dtorname = wxString(_T("~")) + classname;
144
145            // there is only one dtor, so the logic here is simpler
146            if ( (*pp1)->name == dtorname ) {
147                return -1;
148            }
149            else if ( (*pp2)->name == dtorname ) {
150                return 1;
151            }
152
153            // two normal methods
154            return wxStrcmp((*pp1)->name, (*pp2)->name);
155        }
156    }
157
158    static wxString classname;
159};
160
161wxString FunctionDocEntry::classname;
162
163WX_DECLARE_OBJARRAY(FunctionDocEntry, FunctionDocEntries);
164
165#include "wx/arrimpl.cpp"
166
167WX_DEFINE_OBJARRAY(FunctionDocEntries);
168
169// add a function which sanitazes the string before writing it to the file and
170// also capable of delaying output and sorting it before really writing it to
171// the file (done from FlushAll())
172class wxTeXFile : public wxFile
173{
174public:
175    wxTeXFile() { }
176
177    // write a string to file verbatim (should only be used for the strings
178    // inside verbatim environment)
179    void WriteVerbatim(const wxString& s)
180    {
181        m_text += s;
182    }
183
184    // write a string quoting TeX specials in it
185    void WriteTeX(const wxString& s)
186    {
187        wxString t(s);
188        TeXFilter(&t);
189
190        m_text += t;
191    }
192
193    // do write everything to file
194    bool FlushAll()
195    {
196        if ( m_text.empty() )
197            return true;
198
199        if ( !Write(m_text) ) {
200            wxLogError(_T("Failed to output generated documentation."));
201
202            return false;
203        }
204
205        m_text.clear();
206
207        return true;
208    }
209
210private:
211    wxTeXFile(const wxTeXFile&);
212    wxTeXFile& operator=(const wxTeXFile&);
213
214    wxString m_text;
215};
216
217// helper class which manages the classes and function names to ignore for
218// the documentation purposes (used by both HelpGenVisitor and DocManager)
219class IgnoreNamesHandler
220{
221public:
222    IgnoreNamesHandler() : m_ignore(CompareIgnoreListEntries) { }
223    ~IgnoreNamesHandler() { WX_CLEAR_ARRAY(m_ignore); }
224
225    // load file with classes/functions to ignore (add them to the names we
226    // already have)
227    bool AddNamesFromFile(const wxString& filename);
228
229    // return true if we ignore this function
230    bool IgnoreMethod(const wxString& classname,
231                      const wxString& funcname) const
232    {
233        if ( IgnoreClass(classname) )
234            return true;
235
236        IgnoreListEntry ignore(classname, funcname);
237
238        return m_ignore.Index(&ignore) != wxNOT_FOUND;
239    }
240
241    // return true if we ignore this class entirely
242    bool IgnoreClass(const wxString& classname) const
243    {
244        IgnoreListEntry ignore(classname, wxEmptyString);
245
246        return m_ignore.Index(&ignore) != wxNOT_FOUND;
247    }
248
249protected:
250    struct IgnoreListEntry
251    {
252        IgnoreListEntry(const wxString& classname,
253                        const wxString& funcname)
254            : m_classname(classname), m_funcname(funcname)
255        {
256        }
257
258        wxString m_classname;
259        wxString m_funcname;    // if empty, ignore class entirely
260    };
261
262    static int CompareIgnoreListEntries(IgnoreListEntry *first,
263                                        IgnoreListEntry *second);
264
265    // for efficiency, let's sort it
266public: // FIXME: macro requires it
267    WX_DEFINE_SORTED_ARRAY(IgnoreListEntry *, ArrayNamesToIgnore);
268
269protected:
270    ArrayNamesToIgnore m_ignore;
271
272private:
273    IgnoreNamesHandler(const IgnoreNamesHandler&);
274    IgnoreNamesHandler& operator=(const IgnoreNamesHandler&);
275};
276
277// visitor implementation which writes all collected data to a .tex file
278class HelpGenVisitor : public spVisitor
279{
280public:
281    // ctor
282    HelpGenVisitor(const wxString& directoryOut, bool overwrite);
283
284    virtual void VisitFile( spFile& fl );
285    virtual void VisitClass( spClass& cl );
286    virtual void VisitEnumeration( spEnumeration& en );
287    virtual void VisitTypeDef( spTypeDef& td );
288    virtual void VisitPreprocessorLine( spPreprocessorLine& pd );
289    virtual void VisitAttribute( spAttribute& attr );
290    virtual void VisitOperation( spOperation& op );
291    virtual void VisitParameter( spParameter& param );
292
293    void EndVisit();
294
295    // get our `ignore' object
296    IgnoreNamesHandler& GetIgnoreHandler() { return m_ignoreNames; }
297
298    // shut up g++ warning (ain't it stupid?)
299    virtual ~HelpGenVisitor() { }
300
301protected:
302    // (re)initialize the state
303    void Reset();
304
305    // insert documentation for enums/typedefs coming immediately before the
306    // class declaration into the class documentation
307    void InsertTypedefDocs();
308    void InsertEnumDocs();
309
310    // write the headers for corresponding sections (only once)
311    void InsertDataStructuresHeader();
312    void InsertMethodsHeader();
313
314    // terminate the function documentation if it was started
315    void CloseFunction();
316
317    // write out all function docs when there are no more left in this class
318    // after sorting them in alphabetical order
319    void CloseClass();
320
321    wxString  m_directoryOut,   // directory for the output
322              m_fileHeader;     // name of the .h file we parse
323    bool      m_overwrite;      // overwrite existing files?
324    wxTeXFile m_file;           // file we're writing to now
325
326    // state variables
327    bool m_inClass,         // true after file successfully opened
328         m_inTypesSection,  // enums & typedefs go there
329         m_inMethodSection, // functions go here
330         m_isFirstParam;    // first parameter of current function?
331
332    // non empty while parsing a class
333    wxString m_classname;
334
335    // these are only non-empty while parsing a method:
336    wxString m_funcName,    // the function name
337             m_textFunc;    // the function doc text
338
339    // the array containing the documentation entries for the functions in the
340    // class currently being parsed
341    FunctionDocEntries m_arrayFuncDocs;
342
343    // holders for "saved" documentation
344    wxString m_textStoredTypedefs,
345             m_textStoredFunctionComment;
346
347    // for enums we have to use an array as we can't intermix the normal text
348    // and the text inside verbatim environment
349    wxArrayString m_storedEnums,
350                  m_storedEnumsVerb;
351
352    // headers included by this file
353    wxArrayString m_headers;
354
355    // ignore handler: tells us which classes to ignore for doc generation
356    // purposes
357    IgnoreNamesHandler m_ignoreNames;
358
359private:
360    HelpGenVisitor(const HelpGenVisitor&);
361    HelpGenVisitor& operator=(const HelpGenVisitor&);
362};
363
364// documentation manager - a class which parses TeX files and remembers the
365// functions documented in them and can later compare them with all functions
366// found under ctxTop by C++ parser
367class DocManager
368{
369public:
370    DocManager(bool checkParamNames);
371    ~DocManager();
372
373    // returns false on failure
374    bool ParseTeXFile(const wxString& filename);
375
376    // returns false if there were any differences
377    bool DumpDifferences(spContext *ctxTop) const;
378
379    // get our `ignore' object
380    IgnoreNamesHandler& GetIgnoreHandler() { return m_ignoreNames; }
381
382protected:
383    // parsing TeX files
384    // -----------------
385
386    // returns the length of 'match' if the string 'str' starts with it or 0
387    // otherwise
388    static size_t TryMatch(const wxChar *str, const wxChar *match);
389
390    // skip spaces: returns pointer to first non space character (also
391    // updates the value of m_line)
392    const char *SkipSpaces(const char *p)
393    {
394        while ( isspace(*p) ) {
395            if ( *p++ == '\n' )
396                m_line++;
397        }
398
399        return p;
400    }
401
402    // skips characters until the next 'c' in '*pp' unless it ends before in
403    // which case false is returned and pp points to '\0', otherwise true is
404    // returned and pp points to 'c'
405    bool SkipUntil(const char **pp, char c);
406
407    // the same as SkipUntil() but only spaces are skipped: on first non space
408    // character different from 'c' the function stops and returns false
409    bool SkipSpaceUntil(const char **pp, char c);
410
411    // extract the string between {} and modify '*pp' to point at the
412    // character immediately after the closing '}'. The returned string is empty
413    // on error.
414    wxString ExtractStringBetweenBraces(const char **pp);
415
416    // the current file and line while we're in ParseTeXFile (for error
417    // messages)
418    wxString m_filename;
419    size_t   m_line;
420
421    // functions and classes to ignore during diff
422    // -------------------------------------------
423
424    IgnoreNamesHandler m_ignoreNames;
425
426    // information about all functions documented in the TeX file(s)
427    // -------------------------------------------------------------
428
429public: // Note: Sun C++ 5.5 requires TypeInfo and ParamInfo to be public
430
431    // info about a type: for now stored as text string, but must be parsed
432    // further later (to know that "char *" == "char []" - TODO)
433    class TypeInfo
434    {
435    public:
436        TypeInfo(const wxString& type) : m_type(type) { }
437
438        bool operator==(const wxString& type) const { return m_type == type; }
439        bool operator!=(const wxString& type) const { return m_type != type; }
440
441        const wxString& GetName() const { return m_type; }
442
443    private:
444        wxString m_type;
445    };
446
447    friend class ParamInfo; // for access to TypeInfo
448
449    // info abotu a function parameter
450    class ParamInfo
451    {
452    public:
453        ParamInfo(const wxString& type,
454                  const wxString& name,
455                  const wxString& value)
456            : m_type(type), m_name(name), m_value(value)
457        {
458        }
459
460        const TypeInfo& GetType() const { return m_type; }
461        const wxString& GetName() const { return m_name; }
462        const wxString& GetDefValue() const { return m_value; }
463
464    private:
465        TypeInfo m_type;      // type of parameter
466        wxString m_name;      // name
467        wxString m_value;     // default value
468    };
469
470public: // FIXME: macro requires it
471    WX_DEFINE_ARRAY_PTR(ParamInfo *, ArrayParamInfo);
472
473    // info about a function
474    struct MethodInfo
475    {
476    public:
477        enum MethodFlags
478        {
479            Const   = 0x0001,
480            Virtual = 0x0002,
481            Pure    = 0x0004,
482            Static  = 0x0008,
483            Vararg  = 0x0010
484        };
485
486        MethodInfo(const wxString& type,
487                   const wxString& name,
488                   const ArrayParamInfo& params)
489            : m_typeRet(type), m_name(name), m_params(params)
490        {
491            m_flags = 0;
492        }
493
494        void SetFlag(MethodFlags flag) { m_flags |= flag; }
495
496        const TypeInfo& GetType() const { return m_typeRet; }
497        const wxString& GetName() const { return m_name; }
498        const ParamInfo& GetParam(size_t n) const { return *(m_params[n]); }
499        size_t GetParamCount() const { return m_params.GetCount(); }
500
501        bool HasFlag(MethodFlags flag) const { return (m_flags & flag) != 0; }
502
503        ~MethodInfo() { WX_CLEAR_ARRAY(m_params); }
504
505    private:
506        TypeInfo m_typeRet;     // return type
507        wxString m_name;
508        int      m_flags;       // bit mask of the value from the enum above
509
510        ArrayParamInfo m_params;
511    };
512
513    WX_DEFINE_ARRAY_PTR(MethodInfo *, ArrayMethodInfo);
514    WX_DEFINE_ARRAY_PTR(ArrayMethodInfo *, ArrayMethodInfos);
515
516private:
517    // first array contains the names of all classes we found, the second has a
518    // pointer to the array of methods of the given class at the same index as
519    // the class name appears in m_classes
520    wxArrayString    m_classes;
521    ArrayMethodInfos m_methods;
522
523    // are we checking parameter names?
524    bool m_checkParamNames;
525
526private:
527    DocManager(const DocManager&);
528    DocManager& operator=(const DocManager&);
529};
530
531// =============================================================================
532// implementation
533// =============================================================================
534
535static char **g_argv = NULL;
536
537// this function never returns
538static void usage()
539{
540    wxString prog = g_argv[0];
541    wxString basename = prog.AfterLast('/');
542#ifdef __WXMSW__
543    if ( !basename )
544        basename = prog.AfterLast('\\');
545#endif
546    if ( !basename )
547        basename = prog;
548
549    wxLogMessage(
550"usage: %s [global options] <mode> [mode options] <files...>\n"
551"\n"
552"   where global options are:\n"
553"       -q          be quiet\n"
554"       -v          be verbose\n"
555"       -H          give this usage message\n"
556"       -V          print the version info\n"
557"       -i file     file with classes/function to ignore\n"
558"\n"
559"   where mode is one of: dump, diff\n"
560"\n"
561"   dump means generate .tex files for TeX2RTF converter from specified\n"
562"   headers files, mode options are:\n"
563"       -f          overwrite existing files\n"
564"       -o outdir   directory for generated files\n"
565"\n"
566"   diff means compare the set of methods documented .tex file with the\n"
567"   methods declared in the header:\n"
568"           %s diff <file.h> <files.tex...>.\n"
569"   mode specific options are:\n"
570"       -p          do check parameter names (not done by default)\n"
571"\n", basename.c_str(), basename.c_str());
572
573    exit(1);
574}
575
576int main(int argc, char **argv)
577{
578    g_argv = argv;
579
580    wxInitializer initializer;
581    if ( !initializer )
582    {
583        fprintf(stderr, "Failed to initialize the wxWidgets library, aborting.");
584
585        return -1;
586    }
587
588    enum
589    {
590        Mode_None,
591        Mode_Dump,
592        Mode_Diff
593    } mode = Mode_None;
594
595    if ( argc < 2 ) {
596        usage();
597    }
598
599    wxArrayString filesH, filesTeX;
600    wxString directoryOut,      // directory for 'dmup' output
601             ignoreFile;        // file with classes/functions to ignore
602    bool overwrite = false,     // overwrite existing files during 'dump'?
603         paramNames = false;    // check param names during 'diff'?
604
605    for ( int current = 1; current < argc ; current++ ) {
606        // all options have one letter
607        if ( argv[current][0] == '-' ) {
608            if ( argv[current][2] == '\0' ) {
609                switch ( argv[current][1] ) {
610                    case 'v':
611                        // be verbose
612                        wxLog::GetActiveTarget()->SetVerbose();
613                        continue;
614
615                    case 'q':
616                        // be quiet
617                        wxLog::GetActiveTarget()->SetVerbose(false);
618                        continue;
619
620                    case 'H':
621                        // help requested
622                        usage();
623                        // doesn't return
624
625                    case 'V':
626                        // version requested
627                        wxLogMessage("HelpGen version %s\n"
628                                     "(c) 1999-2001 Vadim Zeitlin\n",
629                                     GetVersionString().c_str());
630                        return 0;
631
632                    case 'i':
633                        current++;
634                        if ( current >= argc ) {
635                            wxLogError("-i option requires an argument.");
636
637                            break;
638                        }
639
640                        ignoreFile = argv[current];
641                        continue;
642
643                    case 'p':
644                        if ( mode != Mode_Diff ) {
645                            wxLogError("-p is only valid with diff.");
646
647                            break;
648                        }
649
650                        paramNames = true;
651                        continue;
652
653                    case 'f':
654                        if ( mode != Mode_Dump ) {
655                            wxLogError("-f is only valid with dump.");
656
657                            break;
658                        }
659
660                        overwrite = true;
661                        continue;
662
663                    case 'o':
664                        if ( mode != Mode_Dump ) {
665                            wxLogError("-o is only valid with dump.");
666
667                            break;
668                        }
669
670                        current++;
671                        if ( current >= argc ) {
672                            wxLogError("-o option requires an argument.");
673
674                            break;
675                        }
676
677                        directoryOut = argv[current];
678                        if ( !directoryOut.empty() ) {
679                            // terminate with a '/' if it doesn't have it
680                            switch ( directoryOut.Last() ) {
681                                case '/':
682#ifdef __WXMSW__
683                                case '\\':
684#endif
685                                    break;
686
687                                default:
688                                    directoryOut += '/';
689                            }
690                        }
691                        //else: it's empty, do nothing
692
693                        continue;
694
695                    default:
696                        wxLogError("unknown option '%s'", argv[current]);
697                        break;
698                }
699            }
700            else {
701                wxLogError("only one letter options are allowed, not '%s'.",
702                           argv[current]);
703            }
704
705            // only get here after a break from switch or from else branch of if
706
707            usage();
708        }
709        else {
710            if ( mode == Mode_None ) {
711                if ( strcmp(argv[current], "diff") == 0 )
712                    mode = Mode_Diff;
713                else if ( strcmp(argv[current], "dump") == 0 )
714                    mode = Mode_Dump;
715                else {
716                    wxLogError("unknown mode '%s'.", argv[current]);
717
718                    usage();
719                }
720            }
721            else {
722                if ( mode == Mode_Dump || filesH.IsEmpty() ) {
723                    filesH.Add(argv[current]);
724                }
725                else {
726                    // 2nd files and further are TeX files in diff mode
727                    wxASSERT( mode == Mode_Diff );
728
729                    filesTeX.Add(argv[current]);
730                }
731            }
732        }
733    }
734
735    // create a parser object and a visitor derivation
736    CJSourceParser parser;
737    HelpGenVisitor visitor(directoryOut, overwrite);
738    if ( !ignoreFile.empty() && mode == Mode_Dump )
739        visitor.GetIgnoreHandler().AddNamesFromFile(ignoreFile);
740
741    spContext *ctxTop = NULL;
742
743    // parse all header files
744    size_t nFiles = filesH.GetCount();
745    for ( size_t n = 0; n < nFiles; n++ ) {
746        wxString header = filesH[n];
747        ctxTop = parser.ParseFile(header);
748        if ( !ctxTop ) {
749            wxLogWarning("Header file '%s' couldn't be processed.",
750                         header.c_str());
751        }
752        else if ( mode == Mode_Dump ) {
753            ((spFile *)ctxTop)->m_FileName = header;
754            visitor.VisitAll(*ctxTop);
755            visitor.EndVisit();
756        }
757
758#ifdef __WXDEBUG__
759        if ( 0 && ctxTop )
760            ctxTop->Dump(wxEmptyString);
761#endif // __WXDEBUG__
762    }
763
764    // parse all TeX files
765    if ( mode == Mode_Diff ) {
766        if ( !ctxTop ) {
767            wxLogError("Can't complete diff.");
768
769            // failure
770            return false;
771        }
772
773        DocManager docman(paramNames);
774
775        size_t nFiles = filesTeX.GetCount();
776        for ( size_t n = 0; n < nFiles; n++ ) {
777            wxString file = filesTeX[n];
778            if ( !docman.ParseTeXFile(file) ) {
779                wxLogWarning("TeX file '%s' couldn't be processed.",
780                             file.c_str());
781            }
782        }
783
784        if ( !ignoreFile.empty() )
785            docman.GetIgnoreHandler().AddNamesFromFile(ignoreFile);
786
787        docman.DumpDifferences(ctxTop);
788    }
789
790    return 0;
791}
792
793// -----------------------------------------------------------------------------
794// HelpGenVisitor implementation
795// -----------------------------------------------------------------------------
796
797HelpGenVisitor::HelpGenVisitor(const wxString& directoryOut,
798                               bool overwrite)
799              : m_directoryOut(directoryOut)
800{
801    m_overwrite = overwrite;
802
803    Reset();
804}
805
806void HelpGenVisitor::Reset()
807{
808    m_inClass =
809    m_inTypesSection =
810    m_inMethodSection = false;
811
812    m_classname =
813    m_funcName =
814    m_textFunc =
815    m_textStoredTypedefs =
816    m_textStoredFunctionComment = wxEmptyString;
817
818    m_arrayFuncDocs.Empty();
819
820    m_storedEnums.Empty();
821    m_storedEnumsVerb.Empty();
822    m_headers.Empty();
823}
824
825void HelpGenVisitor::InsertTypedefDocs()
826{
827    m_file.WriteTeX(m_textStoredTypedefs);
828    m_textStoredTypedefs.Empty();
829}
830
831void HelpGenVisitor::InsertEnumDocs()
832{
833    size_t count = m_storedEnums.GetCount();
834    for ( size_t n = 0; n < count; n++ )
835    {
836        m_file.WriteTeX(m_storedEnums[n]);
837        m_file.WriteVerbatim(m_storedEnumsVerb[n] + '\n');
838    }
839
840    m_storedEnums.Empty();
841    m_storedEnumsVerb.Empty();
842}
843
844void HelpGenVisitor::InsertDataStructuresHeader()
845{
846    if ( !m_inTypesSection ) {
847        m_inTypesSection = true;
848
849        m_file.WriteVerbatim("\\wxheading{Data structures}\n\n");
850    }
851}
852
853void HelpGenVisitor::InsertMethodsHeader()
854{
855    if ( !m_inMethodSection ) {
856        m_inMethodSection = true;
857
858        m_file.WriteVerbatim( "\\latexignore{\\rtfignore{\\wxheading{Members}}}\n\n");
859    }
860}
861
862void HelpGenVisitor::CloseFunction()
863{
864    if ( !m_funcName.empty() ) {
865        if ( m_isFirstParam ) {
866            // no params found
867            m_textFunc << "\\void";
868        }
869
870        m_textFunc << "}\n\n";
871
872        if ( !m_textStoredFunctionComment.empty() ) {
873            m_textFunc << m_textStoredFunctionComment << '\n';
874        }
875
876        m_arrayFuncDocs.Add(new FunctionDocEntry(m_funcName, m_textFunc));
877
878        m_funcName.clear();
879    }
880}
881
882void HelpGenVisitor::CloseClass()
883{
884    CloseFunction();
885
886    if ( m_inClass )
887    {
888        size_t count = m_arrayFuncDocs.GetCount();
889        if ( count )
890        {
891            size_t n;
892            FunctionDocEntry::classname = m_classname;
893
894            m_arrayFuncDocs.Sort(FunctionDocEntry::Compare);
895
896            // Now examine each first line and if it's been seen, cut it
897            // off (it's a duplicate \membersection)
898            wxHashTable membersections(wxKEY_STRING);
899
900            for ( n = 0; n < count; n++ )
901            {
902                wxString section(m_arrayFuncDocs[n].text);
903
904                // Strip leading whitespace
905                int pos = section.Find(_T("\\membersection"));
906                if (pos > -1)
907                {
908                    section = section.Mid(pos);
909                }
910
911                wxString ms(section.BeforeFirst(wxT('\n')));
912                if (membersections.Get(ms))
913                {
914                    m_arrayFuncDocs[n].text = section.AfterFirst(wxT('\n'));
915                }
916                else
917                {
918                    membersections.Put(ms.c_str(), & membersections);
919                }
920            }
921
922            for ( n = 0; n < count; n++ ) {
923                m_file.WriteTeX(m_arrayFuncDocs[n].text);
924            }
925
926            m_arrayFuncDocs.Empty();
927        }
928
929        m_inClass = false;
930        m_classname.clear();
931    }
932    m_file.FlushAll();
933}
934
935void HelpGenVisitor::EndVisit()
936{
937    CloseFunction();
938
939    CloseClass();
940
941    m_fileHeader.Empty();
942
943    m_file.FlushAll();
944    if (m_file.IsOpened())
945    {
946        m_file.Flush();
947        m_file.Close();
948    }
949
950    wxLogVerbose("%s: finished generating for the current file.",
951                 GetCurrentTimeFormatted("%H:%M:%S"));
952}
953
954void HelpGenVisitor::VisitFile( spFile& file )
955{
956    m_fileHeader = file.m_FileName;
957    wxLogVerbose("%s: started generating docs for classes from file '%s'...",
958                 GetCurrentTimeFormatted("%H:%M:%S"), m_fileHeader.c_str());
959}
960
961void HelpGenVisitor::VisitClass( spClass& cl )
962{
963    CloseClass();
964
965    if (m_file.IsOpened())
966    {
967        m_file.Flush();
968        m_file.Close();
969    }
970
971    wxString name = cl.GetName();
972
973    if ( m_ignoreNames.IgnoreClass(name) ) {
974        wxLogVerbose("Skipping ignored class '%s'.", name.c_str());
975
976        return;
977    }
978
979    // the file name is built from the class name by removing the leading "wx"
980    // if any and converting it to the lower case
981    wxString filename;
982    if ( name(0, 2) == "wx" ) {
983        filename << name.c_str() + 2;
984    }
985    else {
986        filename << name;
987    }
988
989    filename.MakeLower();
990    filename += ".tex";
991    filename.Prepend(m_directoryOut);
992
993    if ( !m_overwrite && wxFile::Exists(filename) ) {
994        wxLogError("Won't overwrite existing file '%s' - please use '-f'.",
995                   filename.c_str());
996
997        return;
998    }
999
1000    m_inClass = m_file.Open(filename, wxFile::write);
1001    if ( !m_inClass ) {
1002        wxLogError("Can't generate documentation for the class '%s'.",
1003                   name.c_str());
1004
1005        return;
1006    }
1007
1008    m_inMethodSection =
1009    m_inTypesSection = false;
1010
1011    wxLogInfo("Created new file '%s' for class '%s'.",
1012              filename.c_str(), name.c_str());
1013
1014    // write out the header
1015    wxString header;
1016    header.Printf("%%\n"
1017                  "%% automatically generated by HelpGen %s from\n"
1018                  "%% %s at %s\n"
1019                  "%%\n"
1020                  "\n"
1021                  "\n"
1022                  "\\section{\\class{%s}}\\label{%s}\n\n",
1023                  GetVersionString().c_str(),
1024                  m_fileHeader.c_str(),
1025                  GetCurrentTimeFormatted("%d/%b/%y %H:%M:%S"),
1026                  name.c_str(),
1027                  wxString(name).MakeLower().c_str());
1028
1029    m_file.WriteVerbatim(header);
1030
1031    // the entire text we're writing to file
1032    wxString totalText;
1033
1034    // if the header includes other headers they must be related to it... try to
1035    // automatically generate the "See also" clause
1036    if ( !m_headers.IsEmpty() ) {
1037        // correspondence between wxWidgets headers and class names
1038        static const char *headers[] = {
1039            "object",
1040            "defs",
1041            "string",
1042            "dynarray",
1043            "file",
1044            "time",
1045        };
1046
1047        // NULL here means not to insert anything in "See also" for the
1048        // corresponding header
1049        static const char *classes[] = {
1050            NULL,
1051            NULL,
1052            NULL,
1053            NULL,
1054            "wxFile",
1055            "wxTime",
1056        };
1057
1058        wxASSERT_MSG( WXSIZEOF(headers) == WXSIZEOF(classes),
1059                      "arrays must be in sync!" );
1060
1061        wxArrayInt interestingClasses;
1062
1063        size_t count = m_headers.Count(), index;
1064        for ( size_t n = 0; n < count; n++ ) {
1065            wxString baseHeaderName = m_headers[n].Before('.');
1066            if ( baseHeaderName(0, 3) != "wx/" )
1067                continue;
1068
1069            baseHeaderName.erase(0, 3);
1070            for ( index = 0; index < WXSIZEOF(headers); index++ ) {
1071                if ( Stricmp(baseHeaderName, headers[index]) == 0 )
1072                    break;
1073            }
1074
1075            if ( (index < WXSIZEOF(headers)) && classes[index] ) {
1076                // interesting header
1077                interestingClasses.Add(index);
1078            }
1079        }
1080
1081        if ( !interestingClasses.IsEmpty() ) {
1082            // do generate "See also" clause
1083            totalText << "\\wxheading{See also:}\n\n";
1084
1085            count = interestingClasses.Count();
1086            for ( index = 0; index < count; index++ ) {
1087                if ( index > 0 )
1088                    totalText << ", ";
1089
1090                totalText << MakeHelpref(classes[interestingClasses[index]]);
1091            }
1092
1093            totalText << "\n\n";
1094        }
1095    }
1096
1097    // the comment before the class generally explains what is it for so put it
1098    // in place of the class description
1099    if ( cl.HasComments() ) {
1100        wxString comment = GetAllComments(cl);
1101
1102        totalText << '\n' << comment << '\n';
1103    }
1104
1105    // derived from section
1106    wxString derived = "\\wxheading{Derived from}\n\n";
1107
1108    const StrListT& baseClasses = cl.m_SuperClassNames;
1109    if ( baseClasses.size() == 0 ) {
1110        derived << "No base class";
1111    }
1112    else {
1113        bool first = true;
1114        for ( StrListT::const_iterator i = baseClasses.begin();
1115              i != baseClasses.end();
1116              i++ ) {
1117            if ( !first ) {
1118                // separate from the previous one
1119                derived << "\\\\\n";
1120            }
1121            else {
1122                first = false;
1123            }
1124
1125            wxString baseclass = *i;
1126            derived << "\\helpref{" << baseclass << "}";
1127            derived << "{" << baseclass.MakeLower()  << "}";
1128        }
1129    }
1130    totalText << derived << "\n\n";
1131
1132    // include file section
1133    wxString includeFile = "\\wxheading{Include files}\n\n";
1134    includeFile << "<" << m_fileHeader << ">";
1135
1136    totalText << includeFile << "\n\n";
1137
1138    // write all this to file
1139    m_file.WriteTeX(totalText);
1140
1141    // if there were any enums/typedefs before, insert their documentation now
1142    InsertDataStructuresHeader();
1143    InsertTypedefDocs();
1144    InsertEnumDocs();
1145
1146    //m_file.Flush();
1147}
1148
1149void HelpGenVisitor::VisitEnumeration( spEnumeration& en )
1150{
1151    CloseFunction();
1152
1153    if ( m_inMethodSection ) {
1154        // FIXME that's a bug, but tell the user aboit it nevertheless... we
1155        // should be smart enough to process even the enums which come after the
1156        // functions
1157        wxLogWarning("enum '%s' ignored, please put it before the class "
1158                     "methods.", en.GetName().c_str());
1159        return;
1160    }
1161
1162    // simply copy the enum text in the docs
1163    wxString enumeration = GetAllComments(en),
1164             enumerationVerb;
1165
1166    enumerationVerb << _T("\\begin{verbatim}\n")
1167                    << en.m_EnumContent
1168                    << _T("\n\\end{verbatim}\n");
1169
1170    // remember for later use if we're not inside a class yet
1171    if ( !m_inClass ) {
1172        m_storedEnums.Add(enumeration);
1173        m_storedEnumsVerb.Add(enumerationVerb);
1174    }
1175    else {
1176        // write the header for this section if not done yet
1177        InsertDataStructuresHeader();
1178
1179        m_file.WriteTeX(enumeration);
1180        m_file.WriteVerbatim(enumerationVerb);
1181        m_file.WriteVerbatim('\n');
1182    }
1183}
1184
1185void HelpGenVisitor::VisitTypeDef( spTypeDef& td )
1186{
1187    CloseFunction();
1188
1189    if ( m_inMethodSection ) {
1190        // FIXME that's a bug, but tell the user aboit it nevertheless...
1191        wxLogWarning("typedef '%s' ignored, please put it before the class "
1192                     "methods.", td.GetName().c_str());
1193        return;
1194    }
1195
1196    wxString typedefdoc;
1197    typedefdoc << _T("{\\small \\begin{verbatim}\n")
1198               << _T("typedef ") << td.m_OriginalType << _T(' ') << td.GetName()
1199               << _T("\n\\end{verbatim}}\n")
1200               << GetAllComments(td);
1201
1202    // remember for later use if we're not inside a class yet
1203    if ( !m_inClass ) {
1204        if ( !m_textStoredTypedefs.empty() ) {
1205            m_textStoredTypedefs << '\n';
1206        }
1207
1208        m_textStoredTypedefs << typedefdoc;
1209    }
1210    else {
1211        // write the header for this section if not done yet
1212        InsertDataStructuresHeader();
1213
1214        typedefdoc << '\n';
1215        m_file.WriteTeX(typedefdoc);
1216    }
1217}
1218
1219void HelpGenVisitor::VisitPreprocessorLine( spPreprocessorLine& pd )
1220{
1221    switch ( pd.GetStatementType() ) {
1222        case SP_PREP_DEF_INCLUDE_FILE:
1223            m_headers.Add(pd.CPP_GetIncludedFileNeme());
1224            break;
1225
1226        case SP_PREP_DEF_DEFINE_SYMBOL:
1227            // TODO decide if it's a constant and document it if it is
1228            break;
1229    }
1230}
1231
1232void HelpGenVisitor::VisitAttribute( spAttribute& attr )
1233{
1234    CloseFunction();
1235
1236    // only document the public member variables
1237    if ( !m_inClass || !attr.IsPublic() )
1238        return;
1239
1240    wxLogWarning("Ignoring member variable '%s'.", attr.GetName().c_str());
1241}
1242
1243void HelpGenVisitor::VisitOperation( spOperation& op )
1244{
1245    CloseFunction();
1246
1247    if ( !m_inClass ) {
1248        // we don't generate docs right now - either we ignore this class
1249        // entirely or we couldn't open the file
1250        return;
1251    }
1252
1253    if ( !op.IsInClass() ) {
1254        // TODO document global functions
1255        wxLogWarning("skipped global function '%s'.", op.GetName().c_str());
1256
1257        return;
1258    }
1259
1260    if ( op.mVisibility == SP_VIS_PRIVATE ) {
1261        // FIXME should we document protected functions?
1262        return;
1263    }
1264
1265    m_classname = op.GetClass().GetName();
1266    wxString funcname = op.GetName();
1267
1268    if ( m_ignoreNames.IgnoreMethod(m_classname, funcname) ) {
1269        wxLogVerbose("Skipping ignored '%s::%s'.",
1270                     m_classname.c_str(), funcname.c_str());
1271
1272        return;
1273    }
1274
1275    InsertMethodsHeader();
1276
1277    // save state info
1278    m_funcName = funcname;
1279    m_isFirstParam = true;
1280
1281    m_textStoredFunctionComment = GetAllComments(op);
1282
1283    // start function documentation
1284    wxString totalText;
1285
1286    // check for the special case of dtor
1287    wxString dtor;
1288    if ( (funcname[0u] == '~') && (m_classname == funcname.c_str() + 1) ) {
1289        dtor.Printf("\\destruct{%s}", m_classname.c_str());
1290        funcname = dtor;
1291    }
1292
1293    m_textFunc.Printf("\n"
1294        "\\membersection{%s::%s}\\label{%s}\n",
1295        m_classname.c_str(), funcname.c_str(),
1296        MakeLabel(m_classname, funcname).c_str());
1297
1298    wxString constStr;
1299    if(op.mIsConstant) constStr = _T("const");
1300
1301    wxString virtualStr;
1302    if(op.mIsVirtual) virtualStr = _T("virtual ");
1303
1304    wxString func;
1305    func.Printf(_T("\n")
1306                _T("\\%sfunc{%s%s}{%s}{"),
1307                constStr.c_str(),
1308                virtualStr.c_str(),
1309                op.m_RetType.c_str(),
1310                funcname.c_str());
1311    m_textFunc += func;
1312}
1313
1314void HelpGenVisitor::VisitParameter( spParameter& param )
1315{
1316    if ( m_funcName.empty() )
1317        return;
1318
1319    if ( m_isFirstParam ) {
1320        m_isFirstParam = false;
1321    }
1322    else {
1323        m_textFunc << ", ";
1324    }
1325
1326    m_textFunc << "\\param{" << param.m_Type << " }{" << param.GetName();
1327    wxString defvalue = param.m_InitVal;
1328    if ( !defvalue.empty() ) {
1329        m_textFunc << " = " << defvalue;
1330    }
1331
1332    m_textFunc << '}';
1333}
1334
1335// ---------------------------------------------------------------------------
1336// DocManager
1337// ---------------------------------------------------------------------------
1338
1339DocManager::DocManager(bool checkParamNames)
1340{
1341    m_checkParamNames = checkParamNames;
1342}
1343
1344size_t DocManager::TryMatch(const char *str, const char *match)
1345{
1346    size_t lenMatch = 0;
1347    while ( str[lenMatch] == match[lenMatch] ) {
1348        lenMatch++;
1349
1350        if ( match[lenMatch] == '\0' )
1351            return lenMatch;
1352    }
1353
1354    return 0;
1355}
1356
1357bool DocManager::SkipUntil(const char **pp, char c)
1358{
1359    const char *p = *pp;
1360    while ( *p != c ) {
1361        if ( *p == '\0' )
1362            break;
1363
1364        if ( *p == '\n' )
1365            m_line++;
1366
1367        p++;
1368    }
1369
1370    *pp = p;
1371
1372    return *p == c;
1373}
1374
1375bool DocManager::SkipSpaceUntil(const char **pp, char c)
1376{
1377    const char *p = *pp;
1378    while ( *p != c ) {
1379        if ( !isspace(*p) || *p == '\0' )
1380            break;
1381
1382        if ( *p == '\n' )
1383            m_line++;
1384
1385        p++;
1386    }
1387
1388    *pp = p;
1389
1390    return *p == c;
1391}
1392
1393wxString DocManager::ExtractStringBetweenBraces(const char **pp)
1394{
1395    wxString result;
1396
1397    if ( !SkipSpaceUntil(pp, '{') ) {
1398        wxLogWarning("file %s(%d): '{' expected after '\\param'",
1399                     m_filename.c_str(), (int)m_line);
1400
1401    }
1402    else {
1403        const char *startParam = ++*pp; // skip '{'
1404
1405        if ( !SkipUntil(pp, '}') ) {
1406            wxLogWarning("file %s(%d): '}' expected after '\\param'",
1407                         m_filename.c_str(), (int)m_line);
1408        }
1409        else {
1410            result = wxString(startParam, (*pp)++ - startParam);
1411        }
1412    }
1413
1414    return result;
1415}
1416
1417bool DocManager::ParseTeXFile(const wxString& filename)
1418{
1419    m_filename = filename;
1420
1421    wxFile file(m_filename, wxFile::read);
1422    if ( !file.IsOpened() )
1423        return false;
1424
1425    off_t len = file.Length();
1426    if ( len == wxInvalidOffset )
1427        return false;
1428
1429    char *buf = new char[len + 1];
1430    buf[len] = '\0';
1431
1432    if ( file.Read(buf, len) == wxInvalidOffset ) {
1433        delete [] buf;
1434
1435        return false;
1436    }
1437
1438    // reinit everything
1439    m_line = 1;
1440
1441    wxLogVerbose("%s: starting to parse doc file '%s'.",
1442                 GetCurrentTimeFormatted("%H:%M:%S"), m_filename.c_str());
1443
1444    // the name of the class from the last "\membersection" command: we assume
1445    // that the following "\func" or "\constfunc" always documents a method of
1446    // this class (and it should always be like that in wxWidgets documentation)
1447    wxString classname;
1448
1449    for ( const char *current = buf; current - buf < len; current++ ) {
1450        // FIXME parsing is awfully inefficient
1451
1452        if ( *current == '%' ) {
1453            // comment, skip until the end of line
1454            current++;
1455            SkipUntil(&current, '\n');
1456
1457            continue;
1458        }
1459
1460        // all the command we're interested in start with '\\'
1461        while ( *current != '\\' && *current != '\0' ) {
1462            if ( *current++ == '\n' )
1463                m_line++;
1464        }
1465
1466        if ( *current == '\0' ) {
1467            // no more TeX commands left
1468            break;
1469        }
1470
1471        current++; // skip '\\'
1472
1473        enum
1474        {
1475            Nothing,
1476            Func,
1477            ConstFunc,
1478            MemberSect
1479        } foundCommand = Nothing;
1480
1481        size_t lenMatch = TryMatch(current, "func");
1482        if ( lenMatch ) {
1483            foundCommand = Func;
1484        }
1485        else {
1486            lenMatch = TryMatch(current, "constfunc");
1487            if ( lenMatch )
1488                foundCommand = ConstFunc;
1489            else {
1490                lenMatch = TryMatch(current, "membersection");
1491
1492                if ( lenMatch )
1493                    foundCommand = MemberSect;
1494            }
1495        }
1496
1497        if ( foundCommand == Nothing )
1498            continue;
1499
1500        current += lenMatch;
1501
1502        if ( !SkipSpaceUntil(&current, '{') ) {
1503            wxLogWarning("file %s(%d): '{' expected after \\func, "
1504                         "\\constfunc or \\membersection.",
1505                         m_filename.c_str(), (int)m_line);
1506
1507            continue;
1508        }
1509
1510        current++;
1511
1512        if ( foundCommand == MemberSect ) {
1513            // what follows has the form <classname>::<funcname>
1514            const char *startClass = current;
1515            if ( !SkipUntil(&current, ':') || *(current + 1) != ':' ) {
1516                wxLogWarning("file %s(%d): '::' expected after "
1517                             "\\membersection.", m_filename.c_str(), (int)m_line);
1518            }
1519            else {
1520                classname = wxString(startClass, current - startClass);
1521                TeXUnfilter(&classname);
1522            }
1523
1524            continue;
1525        }
1526
1527        // extract the return type
1528        const char *startRetType = current;
1529
1530        if ( !SkipUntil(&current, '}') ) {
1531            wxLogWarning("file %s(%d): '}' expected after return type",
1532                         m_filename.c_str(), (int)m_line);
1533
1534            continue;
1535        }
1536
1537        wxString returnType = wxString(startRetType, current - startRetType);
1538        TeXUnfilter(&returnType);
1539
1540        current++;
1541        if ( !SkipSpaceUntil(&current, '{') ) {
1542            wxLogWarning("file %s(%d): '{' expected after return type",
1543                         m_filename.c_str(), (int)m_line);
1544
1545            continue;
1546        }
1547
1548        current++;
1549        const char *funcEnd = current;
1550        if ( !SkipUntil(&funcEnd, '}') ) {
1551            wxLogWarning("file %s(%d): '}' expected after function name",
1552                         m_filename.c_str(), (int)m_line);
1553
1554            continue;
1555        }
1556
1557        wxString funcName = wxString(current, funcEnd - current);
1558        current = funcEnd + 1;
1559
1560        // trim spaces from both sides
1561        funcName.Trim(false);
1562        funcName.Trim(true);
1563
1564        // special cases: '$...$' may be used for LaTeX inline math, remove the
1565        // '$'s
1566        if ( funcName.Find('$') != wxNOT_FOUND ) {
1567            wxString name;
1568            for ( const char *p = funcName.c_str(); *p != '\0'; p++ ) {
1569                if ( *p != '$' && !isspace(*p) )
1570                    name += *p;
1571            }
1572
1573            funcName = name;
1574        }
1575
1576        // \destruct{foo} is really ~foo
1577        if ( funcName[0u] == '\\' ) {
1578            size_t len = strlen("\\destruct{");
1579            if ( funcName(0, len) != "\\destruct{" ) {
1580                wxLogWarning("file %s(%d): \\destruct expected",
1581                             m_filename.c_str(), (int)m_line);
1582
1583                continue;
1584            }
1585
1586            funcName.erase(0, len);
1587            funcName.Prepend('~');
1588
1589            if ( !SkipSpaceUntil(&current, '}') ) {
1590                wxLogWarning("file %s(%d): '}' expected after destructor",
1591                             m_filename.c_str(), (int)m_line);
1592
1593                continue;
1594            }
1595
1596            funcEnd++;  // there is an extra '}' to count
1597        }
1598
1599        TeXUnfilter(&funcName);
1600
1601        // extract params
1602        current = funcEnd + 1; // skip '}'
1603        if ( !SkipSpaceUntil(&current, '{') ||
1604             (current++, !SkipSpaceUntil(&current, '\\')) ) {
1605            wxLogWarning("file %s(%d): '\\param' or '\\void' expected",
1606                         m_filename.c_str(), (int)m_line);
1607
1608            continue;
1609        }
1610
1611        wxArrayString paramNames, paramTypes, paramValues;
1612
1613        bool isVararg = false;
1614
1615        current++; // skip '\\'
1616        lenMatch = TryMatch(current, "void");
1617        if ( !lenMatch ) {
1618            lenMatch = TryMatch(current, "param");
1619            while ( lenMatch && (current - buf < len) ) {
1620                current += lenMatch;
1621
1622                // now come {paramtype}{paramname}
1623                wxString paramType = ExtractStringBetweenBraces(&current);
1624                if ( !paramType.empty() ) {
1625                    wxString paramText = ExtractStringBetweenBraces(&current);
1626                    if ( !paramText.empty() ) {
1627                        // the param declaration may contain default value
1628                        wxString paramName = paramText.BeforeFirst('='),
1629                                 paramValue = paramText.AfterFirst('=');
1630
1631                        // sanitize all strings
1632                        TeXUnfilter(&paramValue);
1633                        TeXUnfilter(&paramName);
1634                        TeXUnfilter(&paramType);
1635
1636                        paramValues.Add(paramValue);
1637                        paramNames.Add(paramName);
1638                        paramTypes.Add(paramType);
1639                    }
1640                }
1641                else {
1642                    // vararg function?
1643                    wxString paramText = ExtractStringBetweenBraces(&current);
1644                    if ( paramText == "..." ) {
1645                        isVararg = true;
1646                    }
1647                    else {
1648                        wxLogWarning("Parameters of '%s::%s' are in "
1649                                     "incorrect form.",
1650                                     classname.c_str(), funcName.c_str());
1651                    }
1652                }
1653
1654                // what's next?
1655                current = SkipSpaces(current);
1656                if ( *current == ',' || *current == '}' ) {
1657                    current = SkipSpaces(++current);
1658
1659                    lenMatch = TryMatch(current, "\\param");
1660                }
1661                else {
1662                    wxLogWarning("file %s(%d): ',' or '}' expected after "
1663                                 "'\\param'", m_filename.c_str(), (int)m_line);
1664
1665                    continue;
1666                }
1667            }
1668
1669            // if we got here there was no '\\void', so must have some params
1670            if ( paramNames.IsEmpty() ) {
1671                wxLogWarning("file %s(%d): '\\param' or '\\void' expected",
1672                        m_filename.c_str(), (int)m_line);
1673
1674                continue;
1675            }
1676        }
1677
1678        // verbose diagnostic output
1679        wxString paramsAll;
1680        size_t param, paramCount = paramNames.GetCount();
1681        for ( param = 0; param < paramCount; param++ ) {
1682            if ( param != 0 ) {
1683                paramsAll << ", ";
1684            }
1685
1686            paramsAll << paramTypes[param] << ' ' << paramNames[param];
1687        }
1688
1689        wxString constStr;
1690        if (foundCommand == ConstFunc)
1691            constStr = _T(" const");
1692
1693        wxLogVerbose("file %s(%d): found '%s %s::%s(%s)%s'",
1694                     m_filename.c_str(),
1695                     (int)m_line,
1696                     returnType.c_str(),
1697                     classname.c_str(),
1698                     funcName.c_str(),
1699                     paramsAll.c_str(),
1700                     constStr.c_str());
1701
1702        // store the info about the just found function
1703        ArrayMethodInfo *methods;
1704        int index = m_classes.Index(classname);
1705        if ( index == wxNOT_FOUND ) {
1706            m_classes.Add(classname);
1707
1708            methods = new ArrayMethodInfo;
1709            m_methods.Add(methods);
1710        }
1711        else {
1712            methods = m_methods[(size_t)index];
1713        }
1714
1715        ArrayParamInfo params;
1716        for ( param = 0; param < paramCount; param++ ) {
1717            params.Add(new ParamInfo(paramTypes[param],
1718                                     paramNames[param],
1719                                     paramValues[param]));
1720        }
1721
1722        MethodInfo *method = new MethodInfo(returnType, funcName, params);
1723        if ( foundCommand == ConstFunc )
1724            method->SetFlag(MethodInfo::Const);
1725        if ( isVararg )
1726            method->SetFlag(MethodInfo::Vararg);
1727
1728        methods->Add(method);
1729    }
1730
1731    delete [] buf;
1732
1733    wxLogVerbose("%s: finished parsing doc file '%s'.\n",
1734                 GetCurrentTimeFormatted("%H:%M:%S"), m_filename.c_str());
1735
1736    return true;
1737}
1738
1739bool DocManager::DumpDifferences(spContext *ctxTop) const
1740{
1741    typedef MMemberListT::const_iterator MemberIndex;
1742
1743    bool foundDiff = false;
1744
1745    // flag telling us whether the given class was found at all in the header
1746    size_t nClass, countClassesInDocs = m_classes.GetCount();
1747    bool *classExists = new bool[countClassesInDocs];
1748    for ( nClass = 0; nClass < countClassesInDocs; nClass++ ) {
1749        classExists[nClass] = false;
1750    }
1751
1752    // ctxTop is normally an spFile
1753    wxASSERT( ctxTop->GetContextType() == SP_CTX_FILE );
1754
1755    const MMemberListT& classes = ctxTop->GetMembers();
1756    for ( MemberIndex i = classes.begin(); i != classes.end(); i++ ) {
1757        spContext *ctx = *i;
1758        if ( ctx->GetContextType() != SP_CTX_CLASS ) {
1759            // TODO process also global functions, macros, ...
1760            continue;
1761        }
1762
1763        spClass *ctxClass = (spClass *)ctx;
1764        const wxString& nameClass = ctxClass->m_Name;
1765        int index = m_classes.Index(nameClass);
1766        if ( index == wxNOT_FOUND ) {
1767            if ( !m_ignoreNames.IgnoreClass(nameClass) ) {
1768                foundDiff = true;
1769
1770                wxLogError("Class '%s' is not documented at all.",
1771                           nameClass.c_str());
1772            }
1773
1774            // it makes no sense to check for its functions
1775            continue;
1776        }
1777        else {
1778            classExists[index] = true;
1779        }
1780
1781        // array of method descriptions for this class
1782        const ArrayMethodInfo& methods = *(m_methods[index]);
1783        size_t nMethod, countMethods = methods.GetCount();
1784
1785        // flags telling if we already processed given function
1786        bool *methodExists = new bool[countMethods];
1787        for ( nMethod = 0; nMethod < countMethods; nMethod++ ) {
1788            methodExists[nMethod] = false;
1789        }
1790
1791        wxArrayString aOverloadedMethods;
1792
1793        const MMemberListT& functions = ctxClass->GetMembers();
1794        for ( MemberIndex j = functions.begin(); j != functions.end(); j++ ) {
1795            ctx = *j;
1796            if ( ctx->GetContextType() != SP_CTX_OPERATION )
1797                continue;
1798
1799            spOperation *ctxMethod = (spOperation *)ctx;
1800            const wxString& nameMethod = ctxMethod->m_Name;
1801
1802            // find all functions with the same name
1803            wxArrayInt aMethodsWithSameName;
1804            for ( nMethod = 0; nMethod < countMethods; nMethod++ ) {
1805                if ( methods[nMethod]->GetName() == nameMethod )
1806                    aMethodsWithSameName.Add(nMethod);
1807            }
1808
1809            if ( aMethodsWithSameName.IsEmpty() && ctxMethod->IsPublic() ) {
1810                if ( !m_ignoreNames.IgnoreMethod(nameClass, nameMethod) ) {
1811                    foundDiff = true;
1812
1813                    wxLogError("'%s::%s' is not documented.",
1814                               nameClass.c_str(),
1815                               nameMethod.c_str());
1816                }
1817
1818                // don't check params
1819                continue;
1820            }
1821            else if ( aMethodsWithSameName.GetCount() == 1 ) {
1822                index = (size_t)aMethodsWithSameName[0u];
1823                methodExists[index] = true;
1824
1825                if ( m_ignoreNames.IgnoreMethod(nameClass, nameMethod) )
1826                    continue;
1827
1828                if ( !ctxMethod->IsPublic() ) {
1829                    wxLogWarning("'%s::%s' is documented but not public.",
1830                                 nameClass.c_str(),
1831                                 nameMethod.c_str());
1832                }
1833
1834                // check that the flags match
1835                const MethodInfo& method = *(methods[index]);
1836
1837                bool isVirtual = ctxMethod->mIsVirtual;
1838                if ( isVirtual != method.HasFlag(MethodInfo::Virtual) )
1839                {
1840                    wxString virtualStr;
1841                    if(isVirtual)virtualStr = _T("not ");
1842
1843                    wxLogWarning("'%s::%s' is incorrectly documented as %s"
1844                                 "virtual.",
1845                                 nameClass.c_str(),
1846                                 nameMethod.c_str(),
1847                                 virtualStr.c_str());
1848                }
1849
1850                bool isConst = ctxMethod->mIsConstant;
1851                if ( isConst != method.HasFlag(MethodInfo::Const) )
1852                {
1853                    wxString constStr;
1854                    if(isConst)constStr = _T("not ");
1855
1856                    wxLogWarning("'%s::%s' is incorrectly documented as %s"
1857                                 "constant.",
1858                                 nameClass.c_str(),
1859                                 nameMethod.c_str(),
1860                                 constStr.c_str());
1861                }
1862
1863                // check that the params match
1864                const MMemberListT& params = ctxMethod->GetMembers();
1865
1866                if ( params.size() != method.GetParamCount() ) {
1867                    wxLogError("Incorrect number of parameters for '%s::%s' "
1868                               "in the docs: should be %d instead of %d.",
1869                               nameClass.c_str(),
1870                               nameMethod.c_str(),
1871                               (int)params.size(), (int)method.GetParamCount());
1872                }
1873                else {
1874                    size_t nParam = 0;
1875                    for ( MemberIndex k = params.begin();
1876                          k != params.end();
1877                          k++, nParam++ ) {
1878                        ctx = *k;
1879
1880                        // what else can a function have?
1881                        wxASSERT( ctx->GetContextType() == SP_CTX_PARAMETER );
1882
1883                        spParameter *ctxParam = (spParameter *)ctx;
1884                        const ParamInfo& param = method.GetParam(nParam);
1885                        if ( m_checkParamNames &&
1886                             (param.GetName() != ctxParam->m_Name.c_str()) ) {
1887                            foundDiff = true;
1888
1889                            wxLogError("Parameter #%d of '%s::%s' should be "
1890                                       "'%s' and not '%s'.",
1891                                       (int)(nParam + 1),
1892                                       nameClass.c_str(),
1893                                       nameMethod.c_str(),
1894                                       ctxParam->m_Name.c_str(),
1895                                       param.GetName().c_str());
1896
1897                            continue;
1898                        }
1899
1900                        if ( param.GetType() != ctxParam->m_Type ) {
1901                            foundDiff = true;
1902
1903                            wxLogError("Type of parameter '%s' of '%s::%s' "
1904                                       "should be '%s' and not '%s'.",
1905                                       ctxParam->m_Name.c_str(),
1906                                       nameClass.c_str(),
1907                                       nameMethod.c_str(),
1908                                       ctxParam->m_Type.c_str(),
1909                                       param.GetType().GetName().c_str());
1910
1911                            continue;
1912                        }
1913
1914                        if ( param.GetDefValue() != ctxParam->m_InitVal.c_str() ) {
1915                            wxLogWarning("Default value of parameter '%s' of "
1916                                         "'%s::%s' should be '%s' and not "
1917                                         "'%s'.",
1918                                         ctxParam->m_Name.c_str(),
1919                                         nameClass.c_str(),
1920                                         nameMethod.c_str(),
1921                                         ctxParam->m_InitVal.c_str(),
1922                                         param.GetDefValue().c_str());
1923                        }
1924                    }
1925                }
1926            }
1927            else {
1928                // TODO OVER add real support for overloaded methods
1929
1930                if ( m_ignoreNames.IgnoreMethod(nameClass, nameMethod) )
1931                    continue;
1932
1933                if ( aOverloadedMethods.Index(nameMethod) == wxNOT_FOUND ) {
1934                    // mark all methods with this name as existing
1935                    for ( nMethod = 0; nMethod < countMethods; nMethod++ ) {
1936                        if ( methods[nMethod]->GetName() == nameMethod )
1937                            methodExists[nMethod] = true;
1938                    }
1939
1940                    aOverloadedMethods.Add(nameMethod);
1941
1942                    wxLogVerbose("'%s::%s' is overloaded and I'm too "
1943                                 "stupid to find the right match - skipping "
1944                                 "the param and flags checks.",
1945                                 nameClass.c_str(),
1946                                 nameMethod.c_str());
1947                }
1948                //else: warning already given
1949            }
1950        }
1951
1952        for ( nMethod = 0; nMethod < countMethods; nMethod++ ) {
1953            if ( !methodExists[nMethod] ) {
1954                const wxString& nameMethod = methods[nMethod]->GetName();
1955                if ( !m_ignoreNames.IgnoreMethod(nameClass, nameMethod) ) {
1956                    foundDiff = true;
1957
1958                    wxLogError("'%s::%s' is documented but doesn't exist.",
1959                               nameClass.c_str(),
1960                               nameMethod.c_str());
1961                }
1962            }
1963        }
1964
1965        delete [] methodExists;
1966    }
1967
1968    // check that all classes we found in the docs really exist
1969    for ( nClass = 0; nClass < countClassesInDocs; nClass++ ) {
1970        if ( !classExists[nClass] ) {
1971            foundDiff = true;
1972
1973            wxLogError("Class '%s' is documented but doesn't exist.",
1974                       m_classes[nClass].c_str());
1975        }
1976    }
1977
1978    delete [] classExists;
1979
1980    return !foundDiff;
1981}
1982
1983DocManager::~DocManager()
1984{
1985    WX_CLEAR_ARRAY(m_methods);
1986}
1987
1988// ---------------------------------------------------------------------------
1989// IgnoreNamesHandler implementation
1990// ---------------------------------------------------------------------------
1991
1992int IgnoreNamesHandler::CompareIgnoreListEntries(IgnoreListEntry *first,
1993                                                 IgnoreListEntry *second)
1994{
1995    // first compare the classes
1996    int rc = first->m_classname.Cmp(second->m_classname);
1997    if ( rc == 0 )
1998        rc = first->m_funcname.Cmp(second->m_funcname);
1999
2000    return rc;
2001}
2002
2003bool IgnoreNamesHandler::AddNamesFromFile(const wxString& filename)
2004{
2005    wxFile file(filename, wxFile::read);
2006    if ( !file.IsOpened() )
2007        return false;
2008
2009    off_t len = file.Length();
2010    if ( len == wxInvalidOffset )
2011        return false;
2012
2013    char *buf = new char[len + 1];
2014    buf[len] = '\0';
2015
2016    if ( file.Read(buf, len) == wxInvalidOffset ) {
2017        delete [] buf;
2018
2019        return false;
2020    }
2021
2022    wxString line;
2023    for ( const char *current = buf; ; current++ ) {
2024#ifdef __WXMSW__
2025        // skip DOS line separator
2026        if ( *current == '\r' )
2027            current++;
2028#endif // wxMSW
2029
2030        if ( *current == '\n' || *current == '\0' ) {
2031            if ( line[0u] != '#' ) {
2032                if ( line.Find(':') != wxNOT_FOUND ) {
2033                    wxString classname = line.BeforeFirst(':'),
2034                             funcname = line.AfterLast(':');
2035                    m_ignore.Add(new IgnoreListEntry(classname, funcname));
2036                }
2037                else {
2038                    // entire class
2039                    m_ignore.Add(new IgnoreListEntry(line, wxEmptyString));
2040                }
2041            }
2042            //else: comment
2043
2044            if ( *current == '\0' )
2045                break;
2046
2047            line.Empty();
2048        }
2049        else {
2050            line += *current;
2051        }
2052    }
2053
2054    delete [] buf;
2055
2056    return true;
2057}
2058
2059// -----------------------------------------------------------------------------
2060// global function implementation
2061// -----------------------------------------------------------------------------
2062
2063static wxString MakeLabel(const char *classname, const char *funcname)
2064{
2065    wxString label(classname);
2066    if ( funcname && funcname[0] == '\\' ) {
2067        // we may have some special TeX macro - so far only \destruct exists,
2068        // but may be later others will be added
2069        static const char *macros[] = { "destruct" };
2070        static const char *replacement[] = { "dtor" };
2071
2072        size_t n;
2073        for ( n = 0; n < WXSIZEOF(macros); n++ ) {
2074            if ( strncmp(funcname + 1, macros[n], strlen(macros[n])) == 0 ) {
2075                // found
2076                break;
2077            }
2078        }
2079
2080        if ( n == WXSIZEOF(macros) ) {
2081            wxLogWarning("unknown function name '%s' - leaving as is.",
2082                         funcname);
2083        }
2084        else {
2085            funcname = replacement[n];
2086        }
2087    }
2088
2089    if ( funcname ) {
2090        // special treatment for operatorXXX() stuff because the C operators
2091        // are not valid in LaTeX labels
2092        wxString oper;
2093        if ( wxString(funcname).StartsWith("operator", &oper) ) {
2094            label << "operator";
2095
2096            static const struct
2097            {
2098                const char *oper;
2099                const char *name;
2100            } operatorNames[] =
2101            {
2102                { "=",  "assign" },
2103                { "==", "equal" },
2104            };
2105
2106            size_t n;
2107            for ( n = 0; n < WXSIZEOF(operatorNames); n++ ) {
2108                if ( oper == operatorNames[n].oper ) {
2109                    label << operatorNames[n].name;
2110
2111                    break;
2112                }
2113            }
2114
2115            if ( n == WXSIZEOF(operatorNames) ) {
2116                wxLogWarning("unknown operator '%s' - making dummy label.",
2117                             oper.c_str());
2118
2119                label << "unknown";
2120            }
2121        }
2122        else // simply use the func name
2123        {
2124            label << funcname;
2125        }
2126    }
2127
2128    label.MakeLower();
2129
2130    return label;
2131}
2132
2133static wxString MakeHelpref(const char *argument)
2134{
2135    wxString helpref;
2136    helpref << "\\helpref{" << argument << "}{" << MakeLabel(argument) << '}';
2137
2138    return helpref;
2139}
2140
2141static void TeXFilter(wxString* str)
2142{
2143    // TeX special which can be quoted (don't include backslash nor braces as
2144    // we generate them
2145    static wxRegEx reNonSpecialSpecials("[#$%&_]"),
2146                   reAccents("[~^]");
2147
2148    // just quote
2149    reNonSpecialSpecials.ReplaceAll(str, "\\\\\\0");
2150
2151    // can't quote these ones as they produce accents when preceded by
2152    // backslash, so put them inside verb
2153    reAccents.ReplaceAll(str, "\\\\verb|\\0|");
2154}
2155
2156static void TeXUnfilter(wxString* str)
2157{
2158    // FIXME may be done much more quickly
2159    str->Trim(true);
2160    str->Trim(false);
2161
2162    // undo TeXFilter
2163    static wxRegEx reNonSpecialSpecials("\\\\([#$%&_{}])"),
2164                   reAccents("\\\\verb\\|([~^])\\|");
2165
2166    reNonSpecialSpecials.ReplaceAll(str, "\\1");
2167    reAccents.ReplaceAll(str, "\\1");
2168}
2169
2170static wxString GetAllComments(const spContext& ctx)
2171{
2172    wxString comments;
2173    const MCommentListT& commentsList = ctx.GetCommentList();
2174    for ( MCommentListT::const_iterator i = commentsList.begin();
2175          i != commentsList.end();
2176          i++ ) {
2177        wxString comment = (*i)->GetText();
2178
2179        // don't take comments like "// ----------" &c
2180        comment.Trim(false);
2181        if ( !comment.empty() &&
2182              comment == wxString(comment[0u], comment.length() - 1) + '\n' )
2183            comments << "\n";
2184        else
2185            comments << comment;
2186    }
2187
2188    return comments;
2189}
2190
2191static const char *GetCurrentTimeFormatted(const char *timeFormat)
2192{
2193    static char s_timeBuffer[128];
2194    time_t timeNow;
2195    struct tm *ptmNow;
2196
2197    time(&timeNow);
2198    ptmNow = localtime(&timeNow);
2199
2200    strftime(s_timeBuffer, WXSIZEOF(s_timeBuffer), timeFormat, ptmNow);
2201
2202    return s_timeBuffer;
2203}
2204
2205static const wxString GetVersionString()
2206{
2207    wxString version = "$Revision: 34465 $";
2208    wxRegEx("^\\$Revision: 34465 $$").ReplaceFirst(&version, "\\1");
2209    return version;
2210}
2211
2212/*
2213   $Log$
2214   Revision 1.44  2005/05/31 17:47:45  ABX
2215   More warning and error fixes (work in progress with Tinderbox).
2216
2217   Revision 1.43  2005/05/31 15:42:43  ABX
2218   More warning and error fixes (work in progress with Tinderbox).
2219
2220   Revision 1.42  2005/05/31 15:32:49  ABX
2221   More warning and error fixes (work in progress with Tinderbox).
2222
2223   Revision 1.41  2005/05/30 13:06:15  ABX
2224   More warning and error fixes (work in progress with Tinderbox).
2225
2226   Revision 1.40  2005/05/30 11:49:32  ABX
2227   More warning and error fixes (work in progress with Tinderbox).
2228
2229   Revision 1.39  2005/05/30 09:26:42  ABX
2230   More warning and error fixes (work in progress with Tinderbox).
2231
2232   Revision 1.38  2005/05/24 09:06:20  ABX
2233   More fixes and wxWidgets coding standards.
2234
2235   Revision 1.37  2005/05/23 15:22:08  ABX
2236   Initial HelpGen source cleaning.
2237
2238   Revision 1.36  2005/04/07 19:54:58  MW
2239   Workarounds to allow compilation by Sun C++ 5.5
2240
2241   Revision 1.35  2004/12/12 11:03:31  VZ
2242   give an error message if we're built in Unicode mode (in response to bug 1079224)
2243
2244   Revision 1.34  2004/11/23 09:53:31  JS
2245   Changed GPL to wxWindows Licence
2246
2247   Revision 1.33  2004/11/12 03:30:07  RL
2248
2249   Cruft cleanup from MJW, strip the tabs out of sound.cpp
2250
2251   Revision 1.32  2004/11/10 21:02:58  VZ
2252   new set of fixes for problems due to huge files support: drop wxFileSize_t, use wxFileOffset only, make wxInvalidOffset an int (main part of the patch 1063498)
2253
2254   Revision 1.31  2004/10/05 15:38:29  ABX
2255   Warning fixes found under hardest mode of OpenWatcom. Seems clean in Borland, MinGW and DMC.
2256
2257   Revision 1.30  2004/06/18 19:25:50  ABX
2258   Small step in making HelpGen up to date unicode application.
2259
2260   Revision 1.29  2004/06/17 19:00:22  ABX
2261   Warning fixes. Code cleanup. Whitespaces and tabs removed.
2262
2263   Revision 1.28  2004/05/25 11:19:57  JS
2264   More name changes
2265
2266   Revision 1.27  2003/10/13 17:21:30  MBN
2267     Compilation fixes.
2268
2269   Revision 1.26  2003/09/29 15:18:35  MBN
2270     (Blind) compilation fix for Sun compiler.
2271
2272   Revision 1.25  2003/09/03 17:39:27  MBN
2273     Compilation fixes.
2274
2275   Revision 1.24  2003/08/13 22:59:37  VZ
2276   compilation fix
2277
2278   Revision 1.23  2003/06/13 17:05:43  VZ
2279   quote '|' inside regexes (fixes dump mode); fixed crash due to strange HelpGenApp code
2280
2281   Revision 1.22  2002/01/21 21:18:50  JS
2282   Now adds 'include file' heading
2283
2284   Revision 1.21  2002/01/04 11:06:09  JS
2285   Fixed missing membersections bug and also bug with functions not being written
2286   in the right class
2287
2288   Revision 1.20  2002/01/03 14:23:33  JS
2289   Added code to make it not duplicate membersections for overloaded functions
2290
2291   Revision 1.19  2002/01/03 13:34:12  JS
2292   Added FlushAll to CloseClass, otherwise text was only flushed right at the end,
2293   and appeared in one file.
2294
2295   Revision 1.18  2002/01/03 12:02:47  JS
2296   Added main() and corrected VC++ project settings
2297
2298   Revision 1.17  2001/11/30 21:43:35  VZ
2299   now the methods are sorted in the correct order in the generated docs
2300
2301   Revision 1.16  2001/11/28 19:27:33  VZ
2302   HelpGen doesn't work in GUI mode
2303
2304   Revision 1.15  2001/11/22 21:59:58  GD
2305   use "..." instead of <...> for wx headers
2306
2307   Revision 1.14  2001/07/19 13:51:29  VZ
2308   fixes to version string
2309
2310   Revision 1.13  2001/07/19 13:44:57  VZ
2311   1. compilation fixes
2312   2. don't quote special characters inside verbatim environment
2313
2314   Revision 1.12  2000/10/09 13:53:33  juliansmart
2315
2316   Doc corrections; added HelpGen project files
2317
2318   Revision 1.11  2000/07/15 19:50:42  cvsuser
2319   merged 2.2 branch
2320
2321   Revision 1.10.2.2  2000/03/27 15:33:10  VZ
2322   don't trasnform output dir name to lower case
2323
2324   Revision 1.10  2000/03/11 10:05:23  VS
2325   now compiles with wxBase
2326
2327   Revision 1.9  2000/01/16 13:25:21  VS
2328   compilation fixes (gcc)
2329
2330   Revision 1.8  1999/09/13 14:29:39  JS
2331
2332   Made HelpGen into a wxWin app (still uses command-line args); moved includes
2333   into src for simplicity; added VC++ 5 project file
2334
2335   Revision 1.7  1999/02/21 22:32:32  VZ
2336   1. more C++ parser fixes - now it almost parses wx/string.h
2337    a) #if/#ifdef/#else (very) limited support
2338    b) param type fix - now indirection chars are correctly handled
2339    c) class/struct/union distinction
2340    d) public/private fixes
2341    e) Dump() function added - very useful for debugging
2342
2343   2. option to ignore parameter names during 'diff' (in fact, they're ignored
2344      by default, and this option switches it on)
2345
2346   Revision 1.6  1999/02/20 23:00:26  VZ
2347   1. new 'diff' mode which seems to work
2348   2. output files are not overwritten in 'dmup' mode
2349   3. fixes for better handling of const functions and operators
2350    ----------------------------
2351    revision 1.5
2352    date: 1999/02/15 23:07:25;  author: VZ;  state: Exp;  lines: +106 -45
2353    1. Parser improvements
2354     a) const and virtual methods are parsed correctly (not static yet)
2355     b) "const" which is part of the return type is not swallowed
2356
2357    2. HelpGen improvements: -o outputdir parameter added to the cmd line,
2358       "//---------" kind comments discarded now.
2359    ----------------------------
2360    revision 1.4
2361    date: 1999/01/13 14:23:31;  author: JS;  state: Exp;  lines: +4 -4
2362
2363    some tweaks to HelpGen
2364    ----------------------------
2365    revision 1.3
2366    date: 1999/01/09 20:18:03;  author: JS;  state: Exp;  lines: +7 -2
2367
2368    HelpGen starting to compile with VC++
2369    ----------------------------
2370    revision 1.2
2371    date: 1999/01/08 19:46:22;  author: VZ;  state: Exp;  lines: +208 -35
2372
2373    supports typedefs, generates "See also:" and adds "virtual " for virtual
2374    functions
2375    ----------------------------
2376    revision 1.1
2377    date: 1999/01/08 17:45:55;  author: VZ;  state: Exp;
2378
2379    HelpGen is a prototype of the tool for automatic generation of the .tex files
2380    for wxWidgets documentation from C++ headers
2381*/
2382
2383/* vi: set tw=80 et ts=4 sw=4: */
2384