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