// Copyright 2009 The Archiveopteryx Developers #include "docblock.h" #include "function.h" #include "class.h" #include "intro.h" #include "error.h" #include "output.h" #include "singleton.h" /*! \class DocBlock docblock.h The DocBlock class represents a single atom of documentation. A documentation block is written as a C multi-line comment and documents a single class or a single function. DocBlock knows how to generate output for itself. */ /*! Constructs a DocBlock from \a sourceFile, which starts at \a sourceLine, has source \a text and documents \a function. */ DocBlock::DocBlock( File * sourceFile, uint sourceLine, const EString & text, Function * function ) : file( sourceFile ), line( sourceLine ), c( 0 ), f( function ), i( 0 ), t( text ), s( Plain ), isReimp( false ), introduces( false ) { f->setDocBlock( this ); } /*! Constructs a DocBlock from \a sourceFile, which starts at \a sourceLine, has source \a text and documents \a className. */ DocBlock::DocBlock( File * sourceFile, uint sourceLine, const EString & text, Class * className ) : file( sourceFile ), line( sourceLine ), c( className ), f( 0 ), i( 0 ), t( text ), s( Plain ), introduces( false ) { c->setDocBlock( this ); } /*! Constructs a DocBlock from \a sourceFile, which starts at \a sourceLine, has source \a text and documents \a intro. */ DocBlock::DocBlock( File * sourceFile, uint sourceLine, const EString & text, Intro * intro ) : file( sourceFile ), line( sourceLine ), c( 0 ), f( 0 ), i( intro ), t( text ), s( Plain ), introduces( false ) { i->setDocBlock( this ); } /*! Returns true if this DocBlock documents a class, and false if not. */ bool DocBlock::isClass() const { return c != 0; } /*! Returns true if this DocBlock documents an enum type, and false if not. At the moment, enums aren't supported; that'll change. */ bool DocBlock::isEnum() const { return false; } /*! Returns the source text of the documentation. */ EString DocBlock::text() const { return t; } /*! Parses the text() and calls the Output functions on to generate suitable output. */ void DocBlock::generate() { if ( f ) generateFunctionPreamble(); else if ( c ) generateClassPreamble(); else if ( i ) generateIntroPreamble(); int n = 0; uint l = line; uint i = 0; while ( i < t.length() ) { whitespace( i, l ); word( i, l, n++ ); } Output::endParagraph(); if ( f ) { Function * super = f->super(); if ( super ) { Output::addText( "Reimplements " ); Output::addFunction( super->name() + "().", super ); Output::endParagraph(); } } if ( f && !isReimp ) { EString a = f->arguments(); uint i = 0; uint s = 0; char p = ' '; while ( i < a.length() ) { char c = a[i]; if ( c == ',' || c == ')' ) { if ( s > 0 ) { EString name = a.mid( s, i-s ).simplified(); if ( name.endsWith( "[]" ) ) name.truncate( name.length()-2 ); if ( name.find( ' ' ) < 0 && !arguments.contains( name ) ) (void)new Error( file, line, "Undocumented argument: " + name ); s = 0; } } else if ( ( p == '(' || p == ' ' || p == '*' || p == '&' ) && ( ( c >= 'a' && c <= 'z' ) || ( c >= 'A' && c <= 'Z' ) ) ) { s = i; } p = c; i++; } } if ( this->i && !introduces ) (void)new Error( file, line, "\\chapter must contain \\introduces" ); } /*! Steps past whitespace, modifying the character index \a i and the line number \a l. */ void DocBlock::whitespace( uint & i, uint & l ) { bool first = ( i == 0 ); uint ol = l; bool any = false; while ( i < t.length() && ( t[i] == 32 || t[i] == 9 || t[i] == 13 || t[i] == 10 ) ) { if ( t[i] == '\n' ) l++; i++; any = true; } if ( l > ol+1 ) { if ( s == Introduces ) setState( Plain, "(end of paragraph)", l ); checkEndState( ol ); Output::endParagraph(); } else if ( any && !first && s != Introduces ) { Output::addSpace(); } } /*! Steps past and processes a word, which in this context is any nonwhitespace. \a i is the character index, which is moved, \a l is the line number, and \a n is the word number. */ void DocBlock::word( uint & i, uint l, uint n ) { uint j = i; while ( j < t.length() && !( t[j] == 32 || t[j] == 9 || t[j] == 13 || t[j] == 10 ) ) j++; EString w = t.mid( i, j-i ); i = j; if ( w == "RFC" ) { while ( t[j] == ' ' || t[j] == '\n' ) j++; uint start = j; while ( t[j] <= '9' && t[j] >= '0' ) j++; bool ok; uint n = t.mid( start, j-start ).number( &ok ); if ( ok ) { EString rfc( "http://www.rfc-editor.org/rfc/rfc" ); rfc.append( EString::fromNumber( n ) ); rfc.append( ".txt" ); Output::addLink( rfc, "RFC " + EString::fromNumber( n ) ); i = j; } else { plainWord( w, l ); } } else if ( w.lower().startsWith( "http://" ) ) { Output::addLink( w, w ); } else if ( w[0] != '\\' ) { plainWord( w, l ); } else if ( w == "\\a" ) { if ( f ) setState( Argument, w, l ); else (void)new Error( file, l, "\\a is only defined function documentation" ); } else if ( w == "\\introduces" ) { if ( i ) setState( Introduces, w, l ); else (void)new Error( file, l, "\\introduces is only valid after \\chapter" ); introduces = true; } else if ( w == "\\overload" ) { overload( l, n ); } else { (void)new Error( file, l, "udoc directive unknown: " + w ); } } /*! Verifies that all state is appropriate for ending a paragraph or documentation block, and emits appropriate errors if not. \a l must be the line number at which the paragraph/doc block ends. */ void DocBlock::checkEndState( uint l ) { if ( s != Plain ) (void)new Error( file, l, "udoc directive hanging at end of paragraph" ); } /*! Adds the plain word or link \a w to the documentation, reporting an error from line \a l if the link is dangling. */ void DocBlock::plainWord( const EString & w, uint l ) { if ( s == Introduces ) { new Singleton( file, l, w ); Class * c = Class::find( w ); if ( c ) i->addClass( c ); else (void)new Error( file, l, "Cannot find class: " + w ); return; } // find the last character of the word proper uint last = w.length() - 1; while ( last > 0 && ( w[last] == ',' || w[last] == '.' || w[last] == ':' || w[last] == ')' ) ) last--; if ( s == Argument ) { EString name = w.mid( 0, last+1 ); if ( name[0] == '*' ) name = name.mid( 1 ); // yuck, what an evil hack if ( arguments.contains( name ) ) // fine, nothing more to do ; else if ( f->hasArgument( name ) ) arguments.insert( name, (void*)1 ); else (void)new Error( file, l, "No such argument: " + name ); Output::addArgument( w ); setState( Plain, "(after argument name)", l ); return; } // is the word a plausible function name? else if ( w[last] == '(' ) { uint i = 0; while ( i < last && w[i] != '(' ) i++; if ( i > 0 && ( ( w[0] >= 'a' && w[0] <= 'z' ) || ( w[0] >= 'A' && w[0] <= 'Z' ) ) ) { EString name = w.mid( 0, i ); Function * link = 0; Class * scope = c; if ( f && !scope ) scope = f->parent(); if ( name.contains( ':' ) ) { link = Function::find( name ); } else { Class * parent = scope; while ( parent && !link ) { EString tmp = parent->name() + "::" + name; link = Function::find( tmp ); if ( link ) name = tmp; else parent = parent->parent(); } } if ( scope && !link && name != "main" ) { (void)new Error( file, l, "No link target for " + name + "() (in class " + scope->name() + ")" ); } else if ( link && link != f ) { Output::addFunction( w, link ); return; } } } // is it a plausible class name? or enum, or enum value? else if ( w[0] >= 'A' && w[0] <= 'Z' && ( !c || w.mid( 0, last+1 ) != c->name() ) ) { Class * link = Class::find( w.mid( 0, last+1 ) ); Class * thisClass = c; if ( f && !c ) thisClass = f->parent(); if ( link && link != thisClass ) { Output::addClass( w, link ); return; } // here, we could look to see if that looks _very_ much like a // class name, e.g. contains all alphanumerics and at least // one "::", and give an error about undocumented classes if // not. } // nothing doing. just add it as text. Output::addText( w ); } /*! Sets the DocBlock to state \a newState based on directive \a w, and gives an error from line \a l if the transition from the old state to the new is somehow wrong. */ void DocBlock::setState( State newState, const EString & w, uint l ) { if ( s != Plain && newState != Plain ) (void)new Error( file, l, "udoc directive " + w + " negates preceding directive" ); if ( s == Introduces && !i ) (void)new Error( file, l, "udoc directive " + w + " is only valid with \\chapter" ); s = newState; } /*! Handles the "\overload" directive. \a l is the line number where directive was seen and \a n is the word number (0 for the first word in a documentation block). */ void DocBlock::overload( uint l, uint n ) { if ( !f ) (void)new Error( file, l, "\\overload is only meaningful for functions" ); else if ( f->hasOverload() ) (void)new Error( file, l, "\\overload repeated" ); else f->setOverload(); if ( n ) (void)new Error( file, l, "\\overload must be the first directive" ); } static void addWithClass( const EString & s, Class * in ) { Class * c = 0; uint i = 0; while ( c == 0 && i < s.length() ) { if ( s[i] >= 'A' && s[i] <= 'Z' ) { uint j = i; while ( ( s[j] >= 'A' && s[j] <= 'Z' ) || ( s[j] >= 'a' && s[j] <= 'z' ) || ( s[j] >= '0' && s[j] <= '9' ) ) j++; c = Class::find( s.mid( i, j-i ) ); i = j; } i++; } if ( c && c != in ) Output::addClass( s, c ); else Output::addText( s ); } /*! Outputs boilerplante and genetated text to create a suitable headline and lead-in text for this DocBlock's function. */ void DocBlock::generateFunctionPreamble() { Output::startHeadline( f ); addWithClass( f->type(), f->parent() ); Output::addText( " " ); Output::addText( f->name() ); EString a = f->arguments(); if ( a == "()" ) { Output::addText( f->arguments() ); } else { uint s = 0; uint e = 0; while ( e < a.length() ) { while ( e < a.length() && a[e] != ',' ) e++; addWithClass( a.mid( s, e+1-s ), f->parent() ); s = e + 1; while ( a[s] == ' ' ) { Output::addSpace(); s++; } e = s; } } if ( f->isConst() ) Output::addText( " const" ); Output::endParagraph(); } /*! Generates the routine text that introduces the documentation for each class, e.g. what the class inherits. */ void DocBlock::generateClassPreamble() { Output::startHeadline( c ); Output::addText( "Class " ); Output::addText( c->name() ); Output::addText( "." ); Output::endParagraph(); bool p = false; if ( c->parent() ) { Output::addText( "Inherits " ); Output::addClass( c->parent()->name(), c->parent() ); p = true; } List * subclasses = c->subclasses(); if ( subclasses && !subclasses->isEmpty() ) { if ( p ) Output::addText( ". " ); Output::addText( "Inherited by " ); p = true; List::Iterator it( subclasses ); while( it ) { Class * sub = it; ++it; if ( !it ) { Output::addClass( sub->name() + ".", sub ); } else if ( it == subclasses->last() ) { Output::addClass( sub->name(), sub ); Output::addText( " and " ); } else { Output::addClass( sub->name() + ",", sub ); Output::addText( " " ); } } } if ( p ) Output::endParagraph(); List * members = c->members(); if ( !members || members->isEmpty() ) { (void)new Error( file, line, "Class " + c->name() + " has no member functions" ); return; } else { List::Iterator it( members ); while ( it ) ++it; } } /*! Generates routine text to introduce an introduction. Yay! */ void DocBlock::generateIntroPreamble() { Output::startHeadline( i ); }