1 // Copyright 2009 The Archiveopteryx Developers <info@aox.org>
2 
3 #include "docblock.h"
4 
5 #include "function.h"
6 #include "class.h"
7 #include "intro.h"
8 #include "error.h"
9 #include "output.h"
10 #include "singleton.h"
11 
12 
13 /*! \class DocBlock docblock.h
14 
15     The DocBlock class represents a single atom of documentation.
16 
17     A documentation block is written as a C multi-line comment and
18     documents a single class or a single function. DocBlock knows how
19     to generate output for itself.
20 */
21 
22 
23 /*!  Constructs a DocBlock from \a sourceFile, which starts at
24      \a sourceLine, has source \a text and documents \a function.
25 */
26 
DocBlock(File * sourceFile,uint sourceLine,const EString & text,Function * function)27 DocBlock::DocBlock( File * sourceFile, uint sourceLine,
28                     const EString & text, Function * function )
29     : file( sourceFile ), line( sourceLine ),
30       c( 0 ), f( function ), i( 0 ),
31       t( text ), s( Plain ), isReimp( false ), introduces( false )
32 {
33     f->setDocBlock( this );
34 }
35 
36 
37 /*!  Constructs a DocBlock from \a sourceFile, which starts at
38      \a sourceLine, has source \a text and documents \a className.
39 */
40 
DocBlock(File * sourceFile,uint sourceLine,const EString & text,Class * className)41 DocBlock::DocBlock( File * sourceFile, uint sourceLine,
42                     const EString & text, Class * className )
43     : file( sourceFile ), line( sourceLine ),
44       c( className ), f( 0 ), i( 0 ),
45       t( text ), s( Plain ), introduces( false )
46 {
47     c->setDocBlock( this );
48 }
49 
50 
51 /*!  Constructs a DocBlock from \a sourceFile, which starts at
52      \a sourceLine, has source \a text and documents \a intro.
53 */
54 
DocBlock(File * sourceFile,uint sourceLine,const EString & text,Intro * intro)55 DocBlock::DocBlock( File * sourceFile, uint sourceLine,
56                     const EString & text, Intro * intro )
57     : file( sourceFile ), line( sourceLine ),
58       c( 0 ), f( 0 ), i( intro ),
59       t( text ), s( Plain ), introduces( false )
60 {
61     i->setDocBlock( this );
62 }
63 
64 
65 /*! Returns true if this DocBlock documents a class, and false if not. */
66 
isClass() const67 bool DocBlock::isClass() const
68 {
69     return c != 0;
70 }
71 
72 
73 /*! Returns true if this DocBlock documents an enum type, and false if
74     not. At the moment, enums aren't supported; that'll change. */
75 
isEnum() const76 bool DocBlock::isEnum() const
77 {
78     return false;
79 }
80 
81 
82 /*! Returns the source text of the documentation. */
83 
text() const84 EString DocBlock::text() const
85 {
86     return t;
87 }
88 
89 
90 /*! Parses the text() and calls the Output functions on to generate
91     suitable output.
92 */
93 
generate()94 void DocBlock::generate()
95 {
96     if ( f )
97         generateFunctionPreamble();
98     else if ( c )
99         generateClassPreamble();
100     else if ( i )
101         generateIntroPreamble();
102 
103     int n = 0;
104     uint l = line;
105     uint i = 0;
106     while ( i < t.length() ) {
107         whitespace( i, l );
108         word( i, l, n++ );
109     }
110     Output::endParagraph();
111     if ( f ) {
112         Function * super = f->super();
113         if ( super ) {
114             Output::addText( "Reimplements " );
115             Output::addFunction( super->name() + "().", super );
116             Output::endParagraph();
117         }
118     }
119     if ( f && !isReimp ) {
120         EString a = f->arguments();
121         uint i = 0;
122         uint s = 0;
123         char p = ' ';
124         while ( i < a.length() ) {
125             char c = a[i];
126             if ( c == ',' || c == ')' ) {
127                 if ( s > 0 ) {
128                     EString name = a.mid( s, i-s ).simplified();
129                     if ( name.endsWith( "[]" ) )
130                         name.truncate( name.length()-2 );
131                     if ( name.find( ' ' ) < 0 && !arguments.contains( name ) )
132                         (void)new Error( file, line,
133                                          "Undocumented argument: " + name );
134                     s = 0;
135                 }
136             }
137             else if ( ( p == '(' || p == ' ' || p == '*' || p == '&' ) &&
138                       ( ( c >= 'a' && c <= 'z' ) ||
139                         ( c >= 'A' && c <= 'Z' ) ) ) {
140                 s = i;
141             }
142             p = c;
143             i++;
144         }
145     }
146     if ( this->i && !introduces )
147         (void)new Error( file, line, "\\chapter must contain \\introduces" );
148 }
149 
150 
151 /*! Steps past whitespace, modifying the character index \a i and the
152   line number \a l.
153 */
154 
whitespace(uint & i,uint & l)155 void DocBlock::whitespace( uint & i, uint & l )
156 {
157     bool first = ( i == 0 );
158     uint ol = l;
159     bool any = false;
160     while ( i < t.length() && ( t[i] == 32 || t[i] == 9 ||
161                                 t[i] == 13 || t[i] == 10 ) ) {
162         if ( t[i] == '\n' )
163             l++;
164         i++;
165         any = true;
166     }
167     if ( l > ol+1 ) {
168         if ( s == Introduces )
169             setState( Plain, "(end of paragraph)", l );
170         checkEndState( ol );
171         Output::endParagraph();
172     }
173     else if ( any && !first && s != Introduces ) {
174         Output::addSpace();
175     }
176 }
177 
178 
179 /*! Steps past and processes a word, which in this context is any
180     nonwhitespace. \a i is the character index, which is moved, \a l
181     is the line number, and \a n is the word number.
182 */
183 
word(uint & i,uint l,uint n)184 void DocBlock::word( uint & i, uint l, uint n )
185 {
186     uint j = i;
187     while ( j < t.length() && !( t[j] == 32 || t[j] == 9 ||
188                                  t[j] == 13 || t[j] == 10 ) )
189         j++;
190     EString w = t.mid( i, j-i );
191     i = j;
192     if ( w == "RFC" ) {
193         while ( t[j] == ' ' || t[j] == '\n' )
194             j++;
195         uint start = j;
196 
197         while ( t[j] <= '9' && t[j] >= '0' )
198             j++;
199 
200         bool ok;
201         uint n = t.mid( start, j-start ).number( &ok );
202         if ( ok ) {
203             EString rfc( "http://www.rfc-editor.org/rfc/rfc" );
204             rfc.append( EString::fromNumber( n ) );
205             rfc.append( ".txt" );
206 
207             Output::addLink( rfc, "RFC " + EString::fromNumber( n ) );
208 
209             i = j;
210         }
211         else {
212             plainWord( w, l );
213         }
214     }
215     else if ( w.lower().startsWith( "http://" ) ) {
216         Output::addLink( w, w );
217     }
218     else if ( w[0] != '\\' ) {
219         plainWord( w, l );
220     }
221     else if ( w == "\\a" ) {
222         if ( f )
223             setState( Argument, w, l );
224         else
225             (void)new Error( file, l,
226                              "\\a is only defined function documentation" );
227     }
228     else if ( w == "\\introduces" ) {
229         if ( i )
230             setState( Introduces, w, l );
231         else
232             (void)new Error( file, l,
233                              "\\introduces is only valid after \\chapter" );
234         introduces = true;
235     }
236     else if ( w == "\\overload" ) {
237         overload( l, n );
238     }
239     else {
240         (void)new Error( file, l, "udoc directive unknown: " + w );
241     }
242 }
243 
244 
245 /*! Verifies that all state is appropriate for ending a paragraph or
246   documentation block, and emits appropriate errors if not. \a l must
247   be the line number at which the paragraph/doc block ends.
248 */
249 
checkEndState(uint l)250 void DocBlock::checkEndState( uint l )
251 {
252     if ( s != Plain )
253         (void)new Error( file, l,
254                          "udoc directive hanging at end of paragraph" );
255 }
256 
257 
258 /*! Adds the plain word or link \a w to the documentation, reporting
259   an error from line \a l if the link is dangling.
260 */
261 
plainWord(const EString & w,uint l)262 void DocBlock::plainWord( const EString & w, uint l )
263 {
264     if ( s == Introduces ) {
265         new Singleton( file, l, w );
266         Class * c = Class::find( w );
267         if ( c )
268             i->addClass( c );
269         else
270             (void)new Error( file, l, "Cannot find class: " + w );
271         return;
272     }
273     // find the last character of the word proper
274     uint last = w.length() - 1;
275     while ( last > 0 && ( w[last] == ',' || w[last] == '.' ||
276                           w[last] == ':' || w[last] == ')' ) )
277         last--;
278 
279     if ( s == Argument ) {
280         EString name = w.mid( 0, last+1 );
281         if ( name[0] == '*' )
282             name = name.mid( 1 ); // yuck, what an evil hack
283 
284         if ( arguments.contains( name ) )
285             // fine, nothing more to do
286             ;
287         else if ( f->hasArgument( name ) )
288             arguments.insert( name, (void*)1 );
289         else
290             (void)new Error( file, l, "No such argument: " + name );
291         Output::addArgument( w );
292         setState( Plain, "(after argument name)", l );
293         return;
294     }
295     // is the word a plausible function name?
296     else if ( w[last] == '(' ) {
297         uint i = 0;
298         while ( i < last && w[i] != '(' )
299             i++;
300         if ( i > 0 && ( ( w[0] >= 'a' && w[0] <= 'z' ) ||
301                         ( w[0] >= 'A' && w[0] <= 'Z' ) ) ) {
302             EString name = w.mid( 0, i );
303             Function * link = 0;
304             Class * scope = c;
305             if ( f && !scope )
306                 scope = f->parent();
307             if ( name.contains( ':' ) ) {
308                 link = Function::find( name );
309             }
310             else {
311                 Class * parent = scope;
312                 while ( parent && !link ) {
313                     EString tmp = parent->name() + "::" + name;
314                     link = Function::find( tmp );
315                     if ( link )
316                         name = tmp;
317                     else
318                         parent = parent->parent();
319                 }
320             }
321             if ( scope && !link && name != "main" ) {
322                 (void)new Error( file, l,
323                                  "No link target for " + name +
324                                  "() (in class " + scope->name() + ")" );
325             }
326             else if ( link && link != f ) {
327                 Output::addFunction( w, link );
328                 return;
329             }
330         }
331     }
332     // is it a plausible class name? or enum, or enum value?
333     else if ( w[0] >= 'A' && w[0] <= 'Z' &&
334               ( !c || w.mid( 0, last+1 ) != c->name() ) ) {
335         Class * link = Class::find( w.mid( 0, last+1 ) );
336         Class * thisClass = c;
337         if ( f && !c )
338             thisClass = f->parent();
339         if ( link && link != thisClass ) {
340             Output::addClass( w, link );
341             return;
342         }
343         // here, we could look to see if that looks _very_ much like a
344         // class name, e.g. contains all alphanumerics and at least
345         // one "::", and give an error about undocumented classes if
346         // not.
347     }
348 
349     // nothing doing. just add it as text.
350     Output::addText( w );
351 }
352 
353 
354 /*! Sets the DocBlock to state \a newState based on directive \a w,
355   and gives an error from line \a l if the transition from the old
356   state to the new is somehow wrong.
357 */
358 
setState(State newState,const EString & w,uint l)359 void DocBlock::setState( State newState, const EString & w, uint l )
360 {
361     if ( s != Plain && newState != Plain )
362         (void)new Error( file, l,
363                          "udoc directive " + w +
364                          " negates preceding directive" );
365     if ( s == Introduces && !i )
366         (void)new Error( file, l,
367                          "udoc directive " + w +
368                          " is only valid with \\chapter" );
369     s = newState;
370 }
371 
372 
373 /*! Handles the "\overload" directive. \a l is the line number where
374     directive was seen and \a n is the word number (0 for the first
375     word in a documentation block).
376 */
377 
overload(uint l,uint n)378 void DocBlock::overload( uint l, uint n )
379 {
380     if ( !f )
381         (void)new Error( file, l,
382                          "\\overload is only meaningful for functions" );
383     else if ( f->hasOverload() )
384         (void)new Error( file, l,
385                          "\\overload repeated" );
386     else
387         f->setOverload();
388 
389     if ( n )
390         (void)new Error( file, l, "\\overload must be the first directive" );
391 }
392 
393 
addWithClass(const EString & s,Class * in)394 static void addWithClass( const EString & s, Class * in )
395 {
396     Class * c = 0;
397     uint i = 0;
398     while ( c == 0 && i < s.length() ) {
399         if ( s[i] >= 'A' && s[i] <= 'Z' ) {
400             uint j = i;
401             while ( ( s[j] >= 'A' && s[j] <= 'Z' ) ||
402                     ( s[j] >= 'a' && s[j] <= 'z' ) ||
403                     ( s[j] >= '0' && s[j] <= '9' ) )
404                 j++;
405             c = Class::find( s.mid( i, j-i ) );
406             i = j;
407         }
408         i++;
409     }
410     if ( c && c != in )
411         Output::addClass( s, c );
412     else
413         Output::addText( s );
414 }
415 
416 
417 /*! Outputs boilerplante and genetated text to create a suitable
418     headline and lead-in text for this DocBlock's function.
419 */
420 
generateFunctionPreamble()421 void DocBlock::generateFunctionPreamble()
422 {
423     Output::startHeadline( f );
424     addWithClass( f->type(), f->parent() );
425     Output::addText( " " );
426     Output::addText( f->name() );
427     EString a = f->arguments();
428     if ( a == "()" ) {
429         Output::addText( f->arguments() );
430     }
431     else {
432         uint s = 0;
433         uint e = 0;
434         while ( e < a.length() ) {
435             while ( e < a.length() && a[e] != ',' )
436                 e++;
437             addWithClass( a.mid( s, e+1-s ), f->parent() );
438             s = e + 1;
439             while ( a[s] == ' ' ) {
440                 Output::addSpace();
441                 s++;
442             }
443             e = s;
444         }
445     }
446     if ( f->isConst() )
447         Output::addText( " const" );
448     Output::endParagraph();
449 }
450 
451 
452 /*! Generates the routine text that introduces the documentation for
453     each class, e.g. what the class inherits.
454 */
455 
generateClassPreamble()456 void DocBlock::generateClassPreamble()
457 {
458     Output::startHeadline( c );
459     Output::addText( "Class " );
460     Output::addText( c->name() );
461     Output::addText( "." );
462     Output::endParagraph();
463     bool p = false;
464     if ( c->parent() ) {
465         Output::addText( "Inherits " );
466         Output::addClass( c->parent()->name(), c->parent() );
467         p = true;
468     }
469     List<Class> * subclasses = c->subclasses();
470     if ( subclasses && !subclasses->isEmpty() ) {
471         if ( p )
472             Output::addText( ". " );
473         Output::addText( "Inherited by " );
474         p = true;
475         List<Class>::Iterator it( subclasses );
476         while( it ) {
477             Class * sub = it;
478             ++it;
479             if ( !it ) {
480                 Output::addClass( sub->name() + ".", sub );
481             }
482             else if ( it == subclasses->last() ) {
483                 Output::addClass( sub->name(), sub );
484                 Output::addText( " and " );
485             }
486             else {
487                 Output::addClass( sub->name() + ",", sub );
488                 Output::addText( " " );
489             }
490         }
491     }
492     if ( p )
493         Output::endParagraph();
494 
495     List<Function> * members = c->members();
496     if ( !members || members->isEmpty() ) {
497         (void)new Error( file, line,
498                          "Class " + c->name() + " has no member functions" );
499         return;
500     }
501     else {
502         List<Function>::Iterator it( members );
503         while ( it )
504             ++it;
505     }
506 }
507 
508 
509 /*! Generates routine text to introduce an introduction. Yay! */
510 
generateIntroPreamble()511 void DocBlock::generateIntroPreamble()
512 {
513     Output::startHeadline( i );
514 }
515