1 /////////////////////////////////////////////////////////////////////////////
3 // Purpose: Main program file for HelpGen
4 // Author: Vadim Zeitlin <zeitlin@dptmaths.ens-cachan.fr>
8 // Copyright: (c) 1999 VZ
10 /////////////////////////////////////////////////////////////////////////////
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 *"
21 TODO (+ means fixed), see also the change log at the end of the file.
23 (i) small fixes in the current version
25 +1. Quote special TeX characters like '&' and '_' (=> derive from wxFile)
27 3. Document global variables
30 6. Include file name/line number in the "diff" messages?
31 +7. Support for vararg functions
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)
38 (iii) plans for version 3
39 1. Merging with existing files
43 // =============================================================================
45 // =============================================================================
47 // -----------------------------------------------------------------------------
49 // -----------------------------------------------------------------------------
52 #include "wx/wxprec.h"
55 #include <wx/string.h>
57 #include <wx/dynarray.h>
62 // C++ parsing classes
69 // -----------------------------------------------------------------------------
71 // -----------------------------------------------------------------------------
73 // just a copy of argv
74 static char **g_argv
= NULL
;
76 // -----------------------------------------------------------------------------
78 // -----------------------------------------------------------------------------
80 // return the label for the given function name (i.e. argument of \label)
81 static wxString
MakeLabel(const char *classname
, const char *funcname
= NULL
);
83 // return the whole \helpref{arg}{arg_label} string
84 static wxString
MakeHelpref(const char *argument
);
86 // [un]quote special TeX characters (in place)
87 static void TeXFilter(wxString
* str
);
88 static void TeXUnfilter(wxString
* str
); // also trims spaces
90 // get all comments associated with this context
91 static wxString
GetAllComments(const spContext
& ctx
);
93 // get the string with current time (returns pointer to static buffer)
94 // timeFormat is used for the call of strftime(3)
99 static const char *GetCurrentTime(const char *timeFormat
);
101 // -----------------------------------------------------------------------------
103 // -----------------------------------------------------------------------------
105 // add a function which sanitazes the string before writing it to the file
106 class wxTeXFile
: public wxFile
111 bool WriteTeX(const wxString
& s
)
116 return wxFile::Write(t
);
120 wxTeXFile(const wxTeXFile
&);
121 wxTeXFile
& operator=(const wxTeXFile
&);
124 // helper class which manages the classes and function names to ignore for
125 // the documentation purposes (used by both HelpGenVisitor and DocManager)
126 class IgnoreNamesHandler
129 IgnoreNamesHandler() : m_ignore(CompareIgnoreListEntries
) { }
130 ~IgnoreNamesHandler() { WX_CLEAR_ARRAY(m_ignore
); }
132 // load file with classes/functions to ignore (add them to the names we
134 bool AddNamesFromFile(const wxString
& filename
);
136 // return TRUE if we ignore this function
137 bool IgnoreMethod(const wxString
& classname
,
138 const wxString
& funcname
) const
140 if ( IgnoreClass(classname
) )
143 IgnoreListEntry
ignore(classname
, funcname
);
145 return m_ignore
.Index(&ignore
) != wxNOT_FOUND
;
148 // return TRUE if we ignore this class entirely
149 bool IgnoreClass(const wxString
& classname
) const
151 IgnoreListEntry
ignore(classname
, "");
153 return m_ignore
.Index(&ignore
) != wxNOT_FOUND
;
157 struct IgnoreListEntry
159 IgnoreListEntry(const wxString
& classname
,
160 const wxString
& funcname
)
161 : m_classname(classname
), m_funcname(funcname
)
165 wxString m_classname
;
166 wxString m_funcname
; // if empty, ignore class entirely
169 static int CompareIgnoreListEntries(IgnoreListEntry
*first
,
170 IgnoreListEntry
*second
);
172 // for efficiency, let's sort it
173 WX_DEFINE_SORTED_ARRAY(IgnoreListEntry
*, ArrayNamesToIgnore
);
175 ArrayNamesToIgnore m_ignore
;
178 IgnoreNamesHandler(const IgnoreNamesHandler
&);
179 IgnoreNamesHandler
& operator=(const IgnoreNamesHandler
&);
182 // visitor implementation which writes all collected data to a .tex file
183 class HelpGenVisitor
: public spVisitor
187 HelpGenVisitor(const wxString
& directoryOut
, bool overwrite
);
189 virtual void VisitFile( spFile
& fl
);
190 virtual void VisitClass( spClass
& cl
);
191 virtual void VisitEnumeration( spEnumeration
& en
);
192 virtual void VisitTypeDef( spTypeDef
& td
);
193 virtual void VisitPreprocessorLine( spPreprocessorLine
& pd
);
194 virtual void VisitAttribute( spAttribute
& attr
);
195 virtual void VisitOperation( spOperation
& op
);
196 virtual void VisitParameter( spParameter
& param
);
200 // get our `ignore' object
201 IgnoreNamesHandler
& GetIgnoreHandler() { return m_ignoreNames
; }
203 // shut up g++ warning (ain't it stupid?)
204 virtual ~HelpGenVisitor() { }
207 // (re)initialize the state
210 // insert documentation for enums/typedefs coming immediately before the
211 // class declaration into the class documentation
212 void InsertTypedefDocs();
213 void InsertEnumDocs();
215 // write the headers for corresponding sections (only once)
216 void InsertDataStructuresHeader();
217 void InsertMethodsHeader();
219 // terminate the function documentation if it was started
220 void CloseFunction();
222 wxString m_directoryOut
, // directory for the output
223 m_fileHeader
; // name of the .h file we parse
224 bool m_overwrite
; // overwrite existing files?
225 wxTeXFile m_file
; // file we're writing to now
228 bool m_inClass
, // TRUE after file successfully opened
229 m_inTypesSection
, // enums & typedefs go there
230 m_inMethodSection
, // functions go here
231 m_isFirstParam
, // first parameter of current function?
232 m_inFunction
; // we're parsing a function declaration
234 // holders for "saved" documentation
235 wxString m_textStoredEnums
,
236 m_textStoredTypedefs
,
237 m_textStoredFunctionComment
;
239 // headers included by this file
240 wxArrayString m_headers
;
242 // ignore handler: tells us which classes to ignore for doc generation
244 IgnoreNamesHandler m_ignoreNames
;
247 HelpGenVisitor(const HelpGenVisitor
&);
248 HelpGenVisitor
& operator=(const HelpGenVisitor
&);
251 // documentation manager - a class which parses TeX files and remembers the
252 // functions documented in them and can later compare them with all functions
253 // found under ctxTop by C++ parser
257 DocManager(bool checkParamNames
);
260 // returns FALSE on failure
261 bool ParseTeXFile(const wxString
& filename
);
263 // returns FALSE if there were any differences
264 bool DumpDifferences(spContext
*ctxTop
) const;
266 // get our `ignore' object
267 IgnoreNamesHandler
& GetIgnoreHandler() { return m_ignoreNames
; }
273 // returns the length of 'match' if the string 'str' starts with it or 0
275 static size_t TryMatch(const char *str
, const char *match
);
277 // skip spaces: returns pointer to first non space character (also
278 // updates the value of m_line)
279 const char *SkipSpaces(const char *p
)
281 while ( isspace(*p
) ) {
289 // skips characters until the next 'c' in '*pp' unless it ends before in
290 // which case FALSE is returned and pp points to '\0', otherwise TRUE is
291 // returned and pp points to 'c'
292 bool SkipUntil(const char **pp
, char c
);
294 // the same as SkipUntil() but only spaces are skipped: on first non space
295 // character different from 'c' the function stops and returns FALSE
296 bool SkipSpaceUntil(const char **pp
, char c
);
298 // extract the string between {} and modify '*pp' to point at the
299 // character immediately after the closing '}'. The returned string is empty
301 wxString
ExtractStringBetweenBraces(const char **pp
);
303 // the current file and line while we're in ParseTeXFile (for error
308 // functions and classes to ignore during diff
309 // -------------------------------------------
311 IgnoreNamesHandler m_ignoreNames
;
313 // information about all functions documented in the TeX file(s)
314 // -------------------------------------------------------------
316 // info about a type: for now stored as text string, but must be parsed
317 // further later (to know that "char *" == "char []" - TODO)
321 TypeInfo(const wxString
& type
) : m_type(type
) { }
323 bool operator==(const wxString
& type
) const { return m_type
== type
; }
324 bool operator!=(const wxString
& type
) const { return m_type
!= type
; }
326 const wxString
& GetName() const { return m_type
; }
332 // info abotu a function parameter
336 ParamInfo(const wxString
& type
,
337 const wxString
& name
,
338 const wxString
& value
)
339 : m_type(type
), m_name(name
), m_value(value
)
343 const TypeInfo
& GetType() const { return m_type
; }
344 const wxString
& GetName() const { return m_name
; }
345 const wxString
& GetDefValue() const { return m_value
; }
348 TypeInfo m_type
; // type of parameter
349 wxString m_name
; // name
350 wxString m_value
; // default value
353 WX_DEFINE_ARRAY(ParamInfo
*, ArrayParamInfo
);
355 // info about a function
368 MethodInfo(const wxString
& type
,
369 const wxString
& name
,
370 const ArrayParamInfo
& params
)
371 : m_typeRet(type
), m_name(name
), m_params(params
)
376 void SetFlag(MethodFlags flag
) { m_flags
|= flag
; }
378 const TypeInfo
& GetType() const { return m_typeRet
; }
379 const wxString
& GetName() const { return m_name
; }
380 const ParamInfo
& GetParam(size_t n
) const { return *(m_params
[n
]); }
381 size_t GetParamCount() const { return m_params
.GetCount(); }
383 bool HasFlag(MethodFlags flag
) const { return (m_flags
& flag
) != 0; }
385 ~MethodInfo() { WX_CLEAR_ARRAY(m_params
); }
388 TypeInfo m_typeRet
; // return type
390 int m_flags
; // bit mask of the value from the enum above
392 ArrayParamInfo m_params
;
395 WX_DEFINE_ARRAY(MethodInfo
*, ArrayMethodInfo
);
396 WX_DEFINE_ARRAY(ArrayMethodInfo
*, ArrayMethodInfos
);
398 // first array contains the names of all classes we found, the second has a
399 // pointer to the array of methods of the given class at the same index as
400 // the class name appears in m_classes
401 wxArrayString m_classes
;
402 ArrayMethodInfos m_methods
;
404 // are we checking parameter names?
405 bool m_checkParamNames
;
408 DocManager(const DocManager
&);
409 DocManager
& operator=(const DocManager
&);
412 // -----------------------------------------------------------------------------
414 // -----------------------------------------------------------------------------
416 // =============================================================================
418 // =============================================================================
420 // this function never returns
423 wxString prog
= g_argv
[0];
424 wxString basename
= prog
.BeforeLast('/');
427 basename
= prog
.BeforeLast('\\');
433 "usage: %s [global options] <mode> [mode options] <files...>\n"
435 " where global options are:\n"
438 " -H give this usage message\n"
439 " -V print the version info\n"
440 " -i file file with classes/function to ignore\n"
442 " where mode is one of: dump, diff\n"
444 " dump means generate .tex files for TeX2RTF converter from specified\n"
445 " headers files, mode options are:\n"
446 " -f overwrite existing files\n"
447 " -o outdir directory for generated files\n"
449 " diff means compare the set of methods documented .tex file with the\n"
450 " methods declared in the header:\n"
451 " %s diff <file.h> <files.tex...>.\n"
452 " mode specific options are:\n"
453 " -p do check parameter names (not done by default)\n"
454 "\n", basename
.c_str(), basename
.c_str());
459 int main(int argc
, char **argv
)
474 wxArrayString filesH
, filesTeX
;
475 wxString directoryOut
, // directory for 'dmup' output
476 ignoreFile
; // file with classes/functions to ignore
477 bool overwrite
= FALSE
, // overwrite existing files during 'dump'?
478 paramNames
= FALSE
; // check param names during 'diff'?
480 for ( int current
= 1; current
< argc
; current
++ ) {
481 // all options have one letter
482 if ( argv
[current
][0] == '-' ) {
483 if ( argv
[current
][2] == '\0' ) {
484 switch ( argv
[current
][1] ) {
487 wxLog::GetActiveTarget()->SetVerbose();
492 wxLog::GetActiveTarget()->SetVerbose(FALSE
);
501 if ( current
>= argc
) {
502 wxLogError("-i option requires an argument.");
507 ignoreFile
= argv
[current
];
511 if ( mode
!= Mode_Diff
) {
512 wxLogError("-p is only valid with diff.");
521 if ( mode
!= Mode_Dump
) {
522 wxLogError("-f is only valid with dump.");
531 if ( mode
!= Mode_Dump
) {
532 wxLogError("-o is only valid with dump.");
538 if ( current
>= argc
) {
539 wxLogError("-o option requires an argument.");
544 directoryOut
= argv
[current
];
545 if ( !!directoryOut
) {
546 // terminate with a '/' if it doesn't have it
547 switch ( directoryOut
.Last() ) {
558 //else: it's empty, do nothing
563 wxLogError("unknown option '%s'", argv
[current
]);
568 wxLogError("only one letter options are allowed, not '%s'.",
572 // only get here after a break from switch or from else branch of if
577 if ( mode
== Mode_None
) {
578 if ( strcmp(argv
[current
], "diff") == 0 )
580 else if ( strcmp(argv
[current
], "dump") == 0 )
583 wxLogError("unknown mode '%s'.", argv
[current
]);
589 if ( mode
== Mode_Dump
|| filesH
.IsEmpty() ) {
590 filesH
.Add(argv
[current
]);
593 // 2nd files and further are TeX files in diff mode
594 wxASSERT( mode
== Mode_Diff
);
596 filesTeX
.Add(argv
[current
]);
602 // create a parser object and a visitor derivation
603 CJSourceParser parser
;
604 HelpGenVisitor
visitor(directoryOut
, overwrite
);
605 if ( !!ignoreFile
&& mode
== Mode_Dump
)
606 visitor
.GetIgnoreHandler().AddNamesFromFile(ignoreFile
);
608 spContext
*ctxTop
= NULL
;
610 // parse all header files
611 size_t nFiles
= filesH
.GetCount();
612 for ( size_t n
= 0; n
< nFiles
; n
++ ) {
613 wxString header
= filesH
[n
];
614 ctxTop
= parser
.ParseFile(header
);
616 wxLogWarning("Header file '%s' couldn't be processed.",
619 else if ( mode
== Mode_Dump
) {
620 ((spFile
*)ctxTop
)->mFileName
= header
;
621 visitor
.VisitAll(*ctxTop
);
628 #endif // __WXDEBUG__
631 // parse all TeX files
632 if ( mode
== Mode_Diff
) {
634 wxLogError("Can't complete diff.");
640 DocManager
docman(paramNames
);
642 size_t nFiles
= filesTeX
.GetCount();
643 for ( size_t n
= 0; n
< nFiles
; n
++ ) {
644 wxString file
= filesTeX
[n
];
645 if ( !docman
.ParseTeXFile(file
) ) {
646 wxLogWarning("TeX file '%s' couldn't be processed.",
652 docman
.GetIgnoreHandler().AddNamesFromFile(ignoreFile
);
654 docman
.DumpDifferences(ctxTop
);
660 // -----------------------------------------------------------------------------
661 // HelpGenVisitor implementation
662 // -----------------------------------------------------------------------------
664 HelpGenVisitor::HelpGenVisitor(const wxString
& directoryOut
,
666 : m_directoryOut(directoryOut
)
668 m_overwrite
= overwrite
;
673 void HelpGenVisitor::Reset()
678 m_inMethodSection
= FALSE
;
680 m_textStoredTypedefs
=
682 m_textStoredFunctionComment
= "";
686 void HelpGenVisitor::InsertTypedefDocs()
688 m_file
.WriteTeX(m_textStoredTypedefs
);
689 m_textStoredTypedefs
.Empty();
692 void HelpGenVisitor::InsertEnumDocs()
694 m_file
.WriteTeX(m_textStoredEnums
);
695 m_textStoredEnums
.Empty();
698 void HelpGenVisitor::InsertDataStructuresHeader()
700 if ( !m_inTypesSection
) {
701 m_inTypesSection
= TRUE
;
703 m_file
.WriteTeX("\\wxheading{Data structures}\n\n");
707 void HelpGenVisitor::InsertMethodsHeader()
709 if ( !m_inMethodSection
) {
710 m_inMethodSection
= TRUE
;
712 m_file
.WriteTeX( "\\latexignore{\\rtfignore{\\wxheading{Members}}}\n\n");
716 void HelpGenVisitor::CloseFunction()
718 if ( m_inFunction
) {
719 m_inFunction
= FALSE
;
722 if ( m_isFirstParam
) {
724 totalText
<< "\\void";
727 totalText
<< "}\n\n";
729 if ( !m_textStoredFunctionComment
.IsEmpty() )
730 totalText
<< m_textStoredFunctionComment
<< '\n';
732 m_file
.WriteTeX(totalText
);
736 void HelpGenVisitor::EndVisit()
740 m_fileHeader
.Empty();
742 wxLogVerbose("%s: finished generating for the current file.",
743 GetCurrentTime("%H:%M:%S"));
746 void HelpGenVisitor::VisitFile( spFile
& file
)
748 m_fileHeader
= file
.mFileName
;
749 wxLogVerbose("%s: started generating docs for classes from file '%s'...",
750 GetCurrentTime("%H:%M:%S"), m_fileHeader
.c_str());
753 void HelpGenVisitor::VisitClass( spClass
& cl
)
755 m_inClass
= FALSE
; // will be left FALSE on error
757 wxString name
= cl
.GetName();
759 if ( m_ignoreNames
.IgnoreClass(name
) ) {
760 wxLogVerbose("Skipping ignored class '%s'.", name
.c_str());
765 // the file name is built from the class name by removing the leading "wx"
766 // if any and converting it to the lower case
767 wxString filename
= m_directoryOut
;
768 if ( name(0, 2) == "wx" ) {
769 filename
<< name
.c_str() + 2;
775 filename
.MakeLower();
778 if ( !m_overwrite
&& wxFile::Exists(filename
) ) {
779 wxLogError("Won't overwrite existing file '%s' - please use '-f'.",
785 m_inClass
= m_file
.Open(filename
, wxFile::write
);
787 wxLogError("Can't generate documentation for the class '%s'.",
794 m_inTypesSection
= FALSE
;
796 wxLogInfo("Created new file '%s' for class '%s'.",
797 filename
.c_str(), name
.c_str());
799 // the entire text we're writing to file
802 // write out the header
806 "%% automatically generated by HelpGen from\n"
811 "\\section{\\class{%s}}\\label{%s}\n",
812 m_fileHeader
.c_str(), GetCurrentTime("%d/%b/%y %H:%M:%S"),
813 name
.c_str(), wxString(name
).MakeLower().c_str());
815 totalText
<< header
<< '\n';
818 // if the header includes other headers they must be related to it... try to
819 // automatically generate the "See also" clause
820 if ( !m_headers
.IsEmpty() ) {
821 // correspondence between wxWindows headers and class names
822 static const char *headers
[] = {
831 // NULL here means not to insert anything in "See also" for the
832 // corresponding header
833 static const char *classes
[] = {
842 wxASSERT_MSG( WXSIZEOF(headers
) == WXSIZEOF(classes
),
843 "arrays must be in sync!" );
845 wxArrayInt interestingClasses
;
847 size_t count
= m_headers
.Count(), index
;
848 for ( size_t n
= 0; n
< count
; n
++ ) {
849 wxString baseHeaderName
= m_headers
[n
].Before('.');
850 if ( baseHeaderName(0, 3) != "wx/" )
853 baseHeaderName
.erase(0, 3);
854 for ( index
= 0; index
< WXSIZEOF(headers
); index
++ ) {
855 if ( Stricmp(baseHeaderName
, headers
[index
]) == 0 )
859 if ( (index
< WXSIZEOF(headers
)) && classes
[index
] ) {
860 // interesting header
861 interestingClasses
.Add(index
);
865 if ( !interestingClasses
.IsEmpty() ) {
866 // do generate "See also" clause
867 totalText
<< "\\wxheading{See also:}\n\n";
869 count
= interestingClasses
.Count();
870 for ( index
= 0; index
< count
; index
++ ) {
874 totalText
<< MakeHelpref(classes
[interestingClasses
[index
]]);
881 // the comment before the class generally explains what is it for so put it
882 // in place of the class description
883 if ( cl
.HasComments() ) {
884 wxString comment
= GetAllComments(cl
);
886 totalText
<< '\n' << comment
<< '\n';
889 // derived from section
890 wxString derived
= "\\wxheading{Derived from}\n\n";
892 const StrListT
& baseClasses
= cl
.mSuperClassNames
;
893 if ( baseClasses
.size() == 0 ) {
894 derived
<< "No base class";
898 for ( StrListT::const_iterator i
= baseClasses
.begin();
899 i
!= baseClasses
.end();
902 // separate from the previous one
909 wxString baseclass
= *i
;
910 derived
<< "\\helpref{" << baseclass
<< "}";
911 derived
<< "{" << baseclass
.MakeLower() << "}";
914 totalText
<< derived
<< "\n\n";
916 // write all this to file
917 m_file
.WriteTeX(totalText
);
919 // if there were any enums/typedefs before, insert their documentation now
920 InsertDataStructuresHeader();
925 void HelpGenVisitor::VisitEnumeration( spEnumeration
& en
)
929 if ( m_inMethodSection
) {
930 // FIXME that's a bug, but tell the user aboit it nevertheless... we
931 // should be smart enough to process even the enums which come after the
933 wxLogWarning("enum '%s' ignored, please put it before the class "
934 "methods.", en
.GetName().c_str());
938 // simply copy the enum text in the docs
939 wxString enumeration
= GetAllComments(en
);
940 enumeration
<< "{\\small \\begin{verbatim}\n"
942 << "\n\\end{verbatim}}\n";
944 // remember for later use if we're not inside a class yet
946 if ( !m_textStoredEnums
.IsEmpty() ) {
947 m_textStoredEnums
<< '\n';
950 m_textStoredEnums
<< enumeration
;
953 // write the header for this section if not done yet
954 InsertDataStructuresHeader();
957 m_file
.WriteTeX(enumeration
);
961 void HelpGenVisitor::VisitTypeDef( spTypeDef
& td
)
965 if ( m_inMethodSection
) {
966 // FIXME that's a bug, but tell the user aboit it nevertheless...
967 wxLogWarning("typedef '%s' ignored, please put it before the class "
968 "methods.", td
.GetName().c_str());
973 typedefdoc
<< "{\\small \\begin{verbatim}\n"
974 << "typedef " << td
.mOriginalType
<< ' ' << td
.GetName()
975 << "\n\\end{verbatim}}\n"
976 << GetAllComments(td
);
978 // remember for later use if we're not inside a class yet
980 if ( !m_textStoredTypedefs
.IsEmpty() ) {
981 m_textStoredTypedefs
<< '\n';
984 m_textStoredTypedefs
<< typedefdoc
;
987 // write the header for this section if not done yet
988 InsertDataStructuresHeader();
991 m_file
.WriteTeX(typedefdoc
);
995 void HelpGenVisitor::VisitPreprocessorLine( spPreprocessorLine
& pd
)
997 switch ( pd
.GetStatementType() ) {
998 case SP_PREP_DEF_INCLUDE_FILE
:
999 m_headers
.Add(pd
.CPP_GetIncludedFileNeme());
1002 case SP_PREP_DEF_DEFINE_SYMBOL
:
1003 // TODO decide if it's a constant and document it if it is
1008 void HelpGenVisitor::VisitAttribute( spAttribute
& attr
)
1012 // only document the public member variables
1013 if ( !m_inClass
|| !attr
.IsPublic() )
1016 wxLogWarning("Ignoring member variable '%s'.", attr
.GetName().c_str());
1019 void HelpGenVisitor::VisitOperation( spOperation
& op
)
1024 // we don't generate docs right now - either we ignore this class
1025 // entirely or we couldn't open the file
1029 if ( !op
.IsInClass() ) {
1030 // TODO document global functions
1031 wxLogWarning("skipped global function '%s'.", op
.GetName().c_str());
1036 if ( op
.mVisibility
== SP_VIS_PRIVATE
) {
1037 // FIXME should we document protected functions?
1041 wxString funcname
= op
.GetName(),
1042 classname
= op
.GetClass().GetName();
1043 if ( m_ignoreNames
.IgnoreMethod(classname
, funcname
) ) {
1044 wxLogVerbose("Skipping ignored '%s::%s'.",
1045 classname
.c_str(), funcname
.c_str());
1050 InsertMethodsHeader();
1054 m_isFirstParam
= TRUE
;
1056 m_textStoredFunctionComment
= GetAllComments(op
);
1058 // start function documentation
1061 // check for the special case of dtor
1063 if ( (funcname
[0] == '~') && (classname
== funcname
.c_str() + 1) ) {
1064 dtor
.Printf("\\destruct{%s}", classname
.c_str());
1068 totalText
.Printf("\n"
1069 "\\membersection{%s::%s}\\label{%s}\n"
1071 "\\%sfunc{%s%s}{%s}{",
1072 classname
.c_str(), funcname
.c_str(),
1073 MakeLabel(classname
, funcname
).c_str(),
1074 op
.mIsConstant
? "const" : "",
1075 op
.mIsVirtual
? "virtual " : "",
1076 op
.mRetType
.c_str(),
1079 m_file
.WriteTeX(totalText
);
1082 void HelpGenVisitor::VisitParameter( spParameter
& param
)
1084 if ( !m_inFunction
)
1088 if ( m_isFirstParam
) {
1089 m_isFirstParam
= FALSE
;
1095 totalText
<< "\\param{" << param
.mType
<< " }{" << param
.GetName();
1096 wxString defvalue
= param
.mInitVal
;
1097 if ( !defvalue
.IsEmpty() ) {
1098 totalText
<< " = " << defvalue
;
1103 m_file
.WriteTeX(totalText
);
1106 // ---------------------------------------------------------------------------
1108 // ---------------------------------------------------------------------------
1110 DocManager::DocManager(bool checkParamNames
)
1112 m_checkParamNames
= checkParamNames
;
1115 size_t DocManager::TryMatch(const char *str
, const char *match
)
1117 size_t lenMatch
= 0;
1118 while ( str
[lenMatch
] == match
[lenMatch
] ) {
1121 if ( match
[lenMatch
] == '\0' )
1128 bool DocManager::SkipUntil(const char **pp
, char c
)
1130 const char *p
= *pp
;
1146 bool DocManager::SkipSpaceUntil(const char **pp
, char c
)
1148 const char *p
= *pp
;
1150 if ( !isspace(*p
) || *p
== '\0' )
1164 wxString
DocManager::ExtractStringBetweenBraces(const char **pp
)
1168 if ( !SkipSpaceUntil(pp
, '{') ) {
1169 wxLogWarning("file %s(%d): '{' expected after '\\param'",
1170 m_filename
.c_str(), m_line
);
1174 const char *startParam
= ++*pp
; // skip '{'
1176 if ( !SkipUntil(pp
, '}') ) {
1177 wxLogWarning("file %s(%d): '}' expected after '\\param'",
1178 m_filename
.c_str(), m_line
);
1181 result
= wxString(startParam
, (*pp
)++ - startParam
);
1188 bool DocManager::ParseTeXFile(const wxString
& filename
)
1190 m_filename
= filename
;
1192 wxFile
file(m_filename
, wxFile::read
);
1193 if ( !file
.IsOpened() )
1196 off_t len
= file
.Length();
1197 if ( len
== wxInvalidOffset
)
1200 char *buf
= new char[len
+ 1];
1203 if ( file
.Read(buf
, len
) == wxInvalidOffset
) {
1209 // reinit everything
1212 wxLogVerbose("%s: starting to parse doc file '%s'.",
1213 GetCurrentTime("%H:%M:%S"), m_filename
.c_str());
1215 // the name of the class from the last "\membersection" command: we assume
1216 // that the following "\func" or "\constfunc" always documents a method of
1217 // this class (and it should always be like that in wxWindows documentation)
1220 for ( const char *current
= buf
; current
- buf
< len
; current
++ ) {
1221 // FIXME parsing is awfully inefficient
1223 if ( *current
== '%' ) {
1224 // comment, skip until the end of line
1226 SkipUntil(¤t
, '\n');
1231 // all the command we're interested in start with '\\'
1232 while ( *current
!= '\\' && *current
!= '\0' ) {
1233 if ( *current
++ == '\n' )
1237 if ( *current
== '\0' ) {
1238 // no more TeX commands left
1242 current
++; // skip '\\'
1250 } foundCommand
= Nothing
;
1252 size_t lenMatch
= TryMatch(current
, "func");
1254 foundCommand
= Func
;
1257 lenMatch
= TryMatch(current
, "constfunc");
1259 foundCommand
= ConstFunc
;
1261 lenMatch
= TryMatch(current
, "membersection");
1264 foundCommand
= MemberSect
;
1268 if ( foundCommand
== Nothing
)
1271 current
+= lenMatch
;
1273 if ( !SkipSpaceUntil(¤t
, '{') ) {
1274 wxLogWarning("file %s(%d): '{' expected after \\func, "
1275 "\\constfunc or \\membersection.",
1276 m_filename
.c_str(), m_line
);
1283 if ( foundCommand
== MemberSect
) {
1284 // what follows has the form <classname>::<funcname>
1285 const char *startClass
= current
;
1286 if ( !SkipUntil(¤t
, ':') || *(current
+ 1) != ':' ) {
1287 wxLogWarning("file %s(%d): '::' expected after "
1288 "\\membersection.", m_filename
.c_str(), m_line
);
1291 classname
= wxString(startClass
, current
- startClass
);
1292 TeXUnfilter(&classname
);
1298 // extract the return type
1299 const char *startRetType
= current
;
1301 if ( !SkipUntil(¤t
, '}') ) {
1302 wxLogWarning("file %s(%d): '}' expected after return type",
1303 m_filename
.c_str(), m_line
);
1308 wxString returnType
= wxString(startRetType
, current
- startRetType
);
1309 TeXUnfilter(&returnType
);
1312 if ( !SkipSpaceUntil(¤t
, '{') ) {
1313 wxLogWarning("file %s(%d): '{' expected after return type",
1314 m_filename
.c_str(), m_line
);
1320 const char *funcEnd
= current
;
1321 if ( !SkipUntil(&funcEnd
, '}') ) {
1322 wxLogWarning("file %s(%d): '}' expected after function name",
1323 m_filename
.c_str(), m_line
);
1328 wxString funcName
= wxString(current
, funcEnd
- current
);
1329 current
= funcEnd
+ 1;
1331 // trim spaces from both sides
1332 funcName
.Trim(FALSE
);
1333 funcName
.Trim(TRUE
);
1335 // special cases: '$...$' may be used for LaTeX inline math, remove the
1337 if ( funcName
.Find('$') != wxNOT_FOUND
) {
1339 for ( const char *p
= funcName
.c_str(); *p
!= '\0'; p
++ ) {
1340 if ( *p
!= '$' && !isspace(*p
) )
1347 // \destruct{foo} is really ~foo
1348 if ( funcName
[0u] == '\\' ) {
1349 size_t len
= strlen("\\destruct{");
1350 if ( funcName(0, len
) != "\\destruct{" ) {
1351 wxLogWarning("file %s(%d): \\destruct expected",
1352 m_filename
.c_str(), m_line
);
1357 funcName
.erase(0, len
);
1358 funcName
.Prepend('~');
1360 if ( !SkipSpaceUntil(¤t
, '}') ) {
1361 wxLogWarning("file %s(%d): '}' expected after destructor",
1362 m_filename
.c_str(), m_line
);
1367 funcEnd
++; // there is an extra '}' to count
1370 TeXUnfilter(&funcName
);
1373 current
= funcEnd
+ 1; // skip '}'
1374 if ( !SkipSpaceUntil(¤t
, '{') ||
1375 (current
++, !SkipSpaceUntil(¤t
, '\\')) ) {
1376 wxLogWarning("file %s(%d): '\\param' or '\\void' expected",
1377 m_filename
.c_str(), m_line
);
1382 wxArrayString paramNames
, paramTypes
, paramValues
;
1384 bool isVararg
= FALSE
;
1386 current
++; // skip '\\'
1387 lenMatch
= TryMatch(current
, "void");
1389 lenMatch
= TryMatch(current
, "param");
1390 while ( lenMatch
&& (current
- buf
< len
) ) {
1391 current
+= lenMatch
;
1393 // now come {paramtype}{paramname}
1394 wxString paramType
= ExtractStringBetweenBraces(¤t
);
1395 if ( !!paramType
) {
1396 wxString paramText
= ExtractStringBetweenBraces(¤t
);
1397 if ( !!paramText
) {
1398 // the param declaration may contain default value
1399 wxString paramName
= paramText
.BeforeFirst('='),
1400 paramValue
= paramText
.AfterFirst('=');
1402 // sanitize all strings
1403 TeXUnfilter(¶mValue
);
1404 TeXUnfilter(¶mName
);
1405 TeXUnfilter(¶mType
);
1407 paramValues
.Add(paramValue
);
1408 paramNames
.Add(paramName
);
1409 paramTypes
.Add(paramType
);
1414 wxString paramText
= ExtractStringBetweenBraces(¤t
);
1415 if ( paramText
== "..." ) {
1419 wxLogWarning("Parameters of '%s::%s' are in "
1421 classname
.c_str(), funcName
.c_str());
1426 current
= SkipSpaces(current
);
1427 if ( *current
== ',' || *current
== '}' ) {
1428 current
= SkipSpaces(++current
);
1430 lenMatch
= TryMatch(current
, "\\param");
1433 wxLogWarning("file %s(%d): ',' or '}' expected after "
1434 "'\\param'", m_filename
.c_str(), m_line
);
1440 // if we got here there was no '\\void', so must have some params
1441 if ( paramNames
.IsEmpty() ) {
1442 wxLogWarning("file %s(%d): '\\param' or '\\void' expected",
1443 m_filename
.c_str(), m_line
);
1449 // verbose diagnostic output
1451 size_t param
, paramCount
= paramNames
.GetCount();
1452 for ( param
= 0; param
< paramCount
; param
++ ) {
1457 paramsAll
<< paramTypes
[param
] << ' ' << paramNames
[param
];
1460 wxLogVerbose("file %s(%d): found '%s %s::%s(%s)%s'",
1461 m_filename
.c_str(), m_line
,
1466 foundCommand
== ConstFunc
? " const" : "");
1468 // store the info about the just found function
1469 ArrayMethodInfo
*methods
;
1470 int index
= m_classes
.Index(classname
);
1471 if ( index
== wxNOT_FOUND
) {
1472 m_classes
.Add(classname
);
1474 methods
= new ArrayMethodInfo
;
1475 m_methods
.Add(methods
);
1478 methods
= m_methods
[(size_t)index
];
1481 ArrayParamInfo params
;
1482 for ( param
= 0; param
< paramCount
; param
++ ) {
1483 params
.Add(new ParamInfo(paramTypes
[param
],
1485 paramValues
[param
]));
1488 MethodInfo
*method
= new MethodInfo(returnType
, funcName
, params
);
1489 if ( foundCommand
== ConstFunc
)
1490 method
->SetFlag(MethodInfo::Const
);
1492 method
->SetFlag(MethodInfo::Vararg
);
1494 methods
->Add(method
);
1499 wxLogVerbose("%s: finished parsing doc file '%s'.\n",
1500 GetCurrentTime("%H:%M:%S"), m_filename
.c_str());
1505 bool DocManager::DumpDifferences(spContext
*ctxTop
) const
1507 typedef MMemberListT::const_iterator MemberIndex
;
1509 bool foundDiff
= FALSE
;
1511 // flag telling us whether the given class was found at all in the header
1512 size_t nClass
, countClassesInDocs
= m_classes
.GetCount();
1513 bool *classExists
= new bool[countClassesInDocs
];
1514 for ( nClass
= 0; nClass
< countClassesInDocs
; nClass
++ ) {
1515 classExists
[nClass
] = FALSE
;
1518 // ctxTop is normally an spFile
1519 wxASSERT( ctxTop
->GetContextType() == SP_CTX_FILE
);
1521 const MMemberListT
& classes
= ctxTop
->GetMembers();
1522 for ( MemberIndex i
= classes
.begin(); i
!= classes
.end(); i
++ ) {
1523 spContext
*ctx
= *i
;
1524 if ( ctx
->GetContextType() != SP_CTX_CLASS
) {
1525 // TODO process also global functions, macros, ...
1529 spClass
*ctxClass
= (spClass
*)ctx
;
1530 const wxString
& nameClass
= ctxClass
->mName
;
1531 int index
= m_classes
.Index(nameClass
);
1532 if ( index
== wxNOT_FOUND
) {
1533 if ( !m_ignoreNames
.IgnoreClass(nameClass
) ) {
1536 wxLogError("Class '%s' is not documented at all.",
1540 // it makes no sense to check for its functions
1544 classExists
[index
] = TRUE
;
1547 // array of method descriptions for this class
1548 const ArrayMethodInfo
& methods
= *(m_methods
[index
]);
1549 size_t nMethod
, countMethods
= methods
.GetCount();
1551 // flags telling if we already processed given function
1552 bool *methodExists
= new bool[countMethods
];
1553 for ( nMethod
= 0; nMethod
< countMethods
; nMethod
++ ) {
1554 methodExists
[nMethod
] = FALSE
;
1557 wxArrayString aOverloadedMethods
;
1559 const MMemberListT
& functions
= ctxClass
->GetMembers();
1560 for ( MemberIndex j
= functions
.begin(); j
!= functions
.end(); j
++ ) {
1562 if ( ctx
->GetContextType() != SP_CTX_OPERATION
)
1565 spOperation
*ctxMethod
= (spOperation
*)ctx
;
1566 const wxString
& nameMethod
= ctxMethod
->mName
;
1568 // find all functions with the same name
1569 wxArrayInt aMethodsWithSameName
;
1570 for ( nMethod
= 0; nMethod
< countMethods
; nMethod
++ ) {
1571 if ( methods
[nMethod
]->GetName() == nameMethod
)
1572 aMethodsWithSameName
.Add(nMethod
);
1575 if ( aMethodsWithSameName
.IsEmpty() && ctxMethod
->IsPublic() ) {
1576 if ( !m_ignoreNames
.IgnoreMethod(nameClass
, nameMethod
) ) {
1579 wxLogError("'%s::%s' is not documented.",
1581 nameMethod
.c_str());
1584 // don't check params
1587 else if ( aMethodsWithSameName
.GetCount() == 1 ) {
1588 index
= (size_t)aMethodsWithSameName
[0u];
1589 methodExists
[index
] = TRUE
;
1591 if ( m_ignoreNames
.IgnoreMethod(nameClass
, nameMethod
) )
1594 if ( !ctxMethod
->IsPublic() ) {
1595 wxLogWarning("'%s::%s' is documented but not public.",
1597 nameMethod
.c_str());
1600 // check that the flags match
1601 const MethodInfo
& method
= *(methods
[index
]);
1603 bool isVirtual
= ctxMethod
->mIsVirtual
;
1604 if ( isVirtual
!= method
.HasFlag(MethodInfo::Virtual
) ) {
1605 wxLogWarning("'%s::%s' is incorrectly documented as %s"
1609 isVirtual
? "not " : "");
1612 bool isConst
= ctxMethod
->mIsConstant
;
1613 if ( isConst
!= method
.HasFlag(MethodInfo::Const
) ) {
1614 wxLogWarning("'%s::%s' is incorrectly documented as %s"
1618 isConst
? "not " : "");
1621 // check that the params match
1622 const MMemberListT
& params
= ctxMethod
->GetMembers();
1624 if ( params
.size() != method
.GetParamCount() ) {
1625 wxLogError("Incorrect number of parameters for '%s::%s' "
1626 "in the docs: should be %d instead of %d.",
1629 params
.size(), method
.GetParamCount());
1633 for ( MemberIndex k
= params
.begin();
1638 // what else can a function have?
1639 wxASSERT( ctx
->GetContextType() == SP_CTX_PARAMETER
);
1641 spParameter
*ctxParam
= (spParameter
*)ctx
;
1642 const ParamInfo
& param
= method
.GetParam(nParam
);
1643 if ( m_checkParamNames
&&
1644 (param
.GetName() != ctxParam
->mName
) ) {
1647 wxLogError("Parameter #%d of '%s::%s' should be "
1648 "'%s' and not '%s'.",
1652 ctxParam
->mName
.c_str(),
1653 param
.GetName().c_str());
1658 if ( param
.GetType() != ctxParam
->mType
) {
1661 wxLogError("Type of parameter '%s' of '%s::%s' "
1662 "should be '%s' and not '%s'.",
1663 ctxParam
->mName
.c_str(),
1666 ctxParam
->mType
.c_str(),
1667 param
.GetType().GetName().c_str());
1672 if ( param
.GetDefValue() != ctxParam
->mInitVal
) {
1673 wxLogWarning("Default value of parameter '%s' of "
1674 "'%s::%s' should be '%s' and not "
1676 ctxParam
->mName
.c_str(),
1679 ctxParam
->mInitVal
.c_str(),
1680 param
.GetDefValue().c_str());
1686 // TODO OVER add real support for overloaded methods
1688 if ( m_ignoreNames
.IgnoreMethod(nameClass
, nameMethod
) )
1691 if ( aOverloadedMethods
.Index(nameMethod
) == wxNOT_FOUND
) {
1692 // mark all methods with this name as existing
1693 for ( nMethod
= 0; nMethod
< countMethods
; nMethod
++ ) {
1694 if ( methods
[nMethod
]->GetName() == nameMethod
)
1695 methodExists
[nMethod
] = TRUE
;
1698 aOverloadedMethods
.Add(nameMethod
);
1700 wxLogVerbose("'%s::%s' is overloaded and I'm too "
1701 "stupid to find the right match - skipping "
1702 "the param and flags checks.",
1704 nameMethod
.c_str());
1706 //else: warning already given
1710 for ( nMethod
= 0; nMethod
< countMethods
; nMethod
++ ) {
1711 if ( !methodExists
[nMethod
] ) {
1712 const wxString
& nameMethod
= methods
[nMethod
]->GetName();
1713 if ( !m_ignoreNames
.IgnoreMethod(nameClass
, nameMethod
) ) {
1716 wxLogError("'%s::%s' is documented but doesn't exist.",
1718 nameMethod
.c_str());
1723 delete [] methodExists
;
1726 // check that all classes we found in the docs really exist
1727 for ( nClass
= 0; nClass
< countClassesInDocs
; nClass
++ ) {
1728 if ( !classExists
[nClass
] ) {
1731 wxLogError("Class '%s' is documented but doesn't exist.",
1732 m_classes
[nClass
].c_str());
1736 delete [] classExists
;
1741 DocManager::~DocManager()
1743 WX_CLEAR_ARRAY(m_methods
);
1746 // ---------------------------------------------------------------------------
1747 // IgnoreNamesHandler implementation
1748 // ---------------------------------------------------------------------------
1750 int IgnoreNamesHandler::CompareIgnoreListEntries(IgnoreListEntry
*first
,
1751 IgnoreListEntry
*second
)
1753 // first compare the classes
1754 int rc
= first
->m_classname
.Cmp(second
->m_classname
);
1756 rc
= first
->m_funcname
.Cmp(second
->m_funcname
);
1761 bool IgnoreNamesHandler::AddNamesFromFile(const wxString
& filename
)
1763 wxFile
file(filename
, wxFile::read
);
1764 if ( !file
.IsOpened() )
1767 off_t len
= file
.Length();
1768 if ( len
== wxInvalidOffset
)
1771 char *buf
= new char[len
+ 1];
1774 if ( file
.Read(buf
, len
) == wxInvalidOffset
) {
1781 for ( const char *current
= buf
; ; current
++ ) {
1783 // skip DOS line separator
1784 if ( *current
== '\r' )
1788 if ( *current
== '\n' || *current
== '\0' ) {
1789 if ( line
[0u] != '#' ) {
1790 if ( line
.Find(':') != wxNOT_FOUND
) {
1791 wxString classname
= line
.BeforeFirst(':'),
1792 funcname
= line
.AfterLast(':');
1793 m_ignore
.Add(new IgnoreListEntry(classname
, funcname
));
1797 m_ignore
.Add(new IgnoreListEntry(line
, ""));
1802 if ( *current
== '\0' )
1817 // -----------------------------------------------------------------------------
1818 // global function implementation
1819 // -----------------------------------------------------------------------------
1821 static wxString
MakeLabel(const char *classname
, const char *funcname
)
1823 wxString
label(classname
);
1824 if ( funcname
&& funcname
[0] == '\\' ) {
1825 // we may have some special TeX macro - so far only \destruct exists,
1826 // but may be later others will be added
1827 static const char *macros
[] = { "destruct" };
1828 static const char *replacement
[] = { "dtor" };
1831 for ( n
= 0; n
< WXSIZEOF(macros
); n
++ ) {
1832 if ( strncmp(funcname
+ 1, macros
[n
], strlen(macros
[n
])) == 0 ) {
1838 if ( n
== WXSIZEOF(macros
) ) {
1839 wxLogWarning("unknown function name '%s' - leaving as is.",
1843 funcname
= replacement
[n
];
1855 static wxString
MakeHelpref(const char *argument
)
1858 helpref
<< "\\helpref{" << argument
<< "}{" << MakeLabel(argument
) << '}';
1863 static void TeXUnfilter(wxString
* str
)
1865 // FIXME may be done much more quickly
1869 str
->Replace("\\&", "&");
1870 str
->Replace("\\_", "_");
1873 static void TeXFilter(wxString
* str
)
1875 // FIXME may be done much more quickly
1876 str
->Replace("&", "\\&");
1877 str
->Replace("_", "\\_");
1880 static wxString
GetAllComments(const spContext
& ctx
)
1883 const MCommentListT
& commentsList
= ctx
.GetCommentList();
1884 for ( MCommentListT::const_iterator i
= commentsList
.begin();
1885 i
!= commentsList
.end();
1887 wxString comment
= (*i
)->GetText();
1889 // don't take comments like "// ----------" &c
1890 comment
.Trim(FALSE
);
1892 comment
== wxString(comment
[0u], comment
.length() - 1) + '\n' )
1895 comments
<< comment
;
1901 static const char *GetCurrentTime(const char *timeFormat
)
1903 static char s_timeBuffer
[128];
1908 ptmNow
= localtime(&timeNow
);
1910 strftime(s_timeBuffer
, WXSIZEOF(s_timeBuffer
), timeFormat
, ptmNow
);
1912 return s_timeBuffer
;
1917 Revision 1.7 1999/02/21 22:32:32 VZ
1918 1. more C++ parser fixes - now it almost parses wx/string.h
1919 a) #if/#ifdef/#else (very) limited support
1920 b) param type fix - now indirection chars are correctly handled
1921 c) class/struct/union distinction
1922 d) public/private fixes
1923 e) Dump() function added - very useful for debugging
1925 2. option to ignore parameter names during 'diff' (in fact, they're ignored
1926 by default, and this option switches it on)
1928 Revision 1.6 1999/02/20 23:00:26 VZ
1929 1. new 'diff' mode which seems to work
1930 2. output files are not overwritten in 'dmup' mode
1931 3. fixes for better handling of const functions and operators
1932 ----------------------------
1934 date: 1999/02/15 23:07:25; author: VZ; state: Exp; lines: +106 -45
1935 1. Parser improvements
1936 a) const and virtual methods are parsed correctly (not static yet)
1937 b) "const" which is part of the return type is not swallowed
1939 2. HelpGen improvements: -o outputdir parameter added to the cmd line,
1940 "//---------" kind comments discarded now.
1941 ----------------------------
1943 date: 1999/01/13 14:23:31; author: JS; state: Exp; lines: +4 -4
1945 some tweaks to HelpGen
1946 ----------------------------
1948 date: 1999/01/09 20:18:03; author: JS; state: Exp; lines: +7 -2
1950 HelpGen starting to compile with VC++
1951 ----------------------------
1953 date: 1999/01/08 19:46:22; author: VZ; state: Exp; lines: +208 -35
1955 supports typedefs, generates "See also:" and adds "virtual " for virtual
1957 ----------------------------
1959 date: 1999/01/08 17:45:55; author: VZ; state: Exp;
1961 HelpGen is a prototype of the tool for automatic generation of the .tex files
1962 for wxWindows documentation from C++ headers
1965 /* vi: set tw=80 et ts=4 sw=4: */