/* Copyright (C) 1994-2007 Frans Slothouber, Jacco van Weert, Petteri Kettunen, Bernd Koesling, Thomas Aglassinger, Anthon Pang, Stefan Kost, David Druffner, Sasha Vasko, Kai Hofmann, Thierry Pierron, Friedrich Haase, and Gergely Budai. This file is part of ROBODoc ROBODoc is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ /****h* ROBODoc/Analyser * NAME * Analyser -- Functions to scan source and collect headers * FUNCTION * This module provides the functions to scan a sourcefile and * collect all the headers. * ***** * $Id: analyser.c,v 1.73 2007/07/10 19:13:51 gumpu Exp $ */ #include #include #include #include #include #include #include "robodoc.h" #include "globals.h" #include "headers.h" #include "headertypes.h" #include "items.h" #include "util.h" #include "links.h" #include "analyser.h" #include "document.h" #include "file.h" #include "part.h" #include "roboconfig.h" #ifdef DMALLOC #include #endif static int ToBeAdded( struct RB_Document *document, struct RB_header *header ); static int Find_Header_Name( FILE *, struct RB_header * ); static struct RB_header *Grab_Header( FILE *sourcehandle, struct RB_Document *arg_document ); static char *Function_Name( char *header_name ); static char *Module_Name( char *header_name ); static int Find_End_Marker( FILE *document, struct RB_header *new_header ); struct RB_HeaderType *AnalyseHeaderType( char **cur_char, int *is_internal ); static struct RB_HeaderType *RB_Find_Marker( FILE *document, int *is_internal, int reuse_previous_line ); static int Analyse_Items( struct RB_header *arg_header ); static int Is_ListItem_Start( char *arg_line, int arg_indent ); /****f* Analyser/Is_Pipe_Marker * NAME * Is_Pipe_Marker * FUNCTION * Check for "pipe" markers e.g. "|html ". * SYNOPSIS */ static char *Is_Pipe_Marker( char *cur_char, int *pipe_mode ) /* * RESULT * Pointer to the data to be piped to document or in case no pointers * are found. * SEE ALSO * RB_Check_Pipe * SOURCE */ { char *s = cur_char + 1; *pipe_mode = -1; if ( *cur_char == '|' && *s ) { if ( strncmp( "html ", s, 5 ) == 0 ) { *pipe_mode = HTML; return ( s + 5 ); } else if ( strncmp( "latex ", s, 6 ) == 0 ) { *pipe_mode = LATEX; return ( s + 6 ); } else if ( strncmp( "rtf ", s, 4 ) == 0 ) { *pipe_mode = RTF; return ( s + 4 ); } else if ( strncmp( "dbxml ", s, 6 ) == 0 ) { *pipe_mode = XMLDOCBOOK; return ( s + 6 ); } else if ( strncmp( "ascii ", s, 6 ) == 0 ) { *pipe_mode = ASCII; return ( s + 6 ); } } return ( char * ) NULL; } /*****/ /****f* Analyser/Is_Tool * SYNOPSIS */ static char *Is_Tool( char *cur_char, enum ItemLineKind *itemkind, int *tool_active ) /* * FUNCTION * Checks for tool start and end markers * SOURCE */ { char *s = cur_char + 1; if ( *cur_char == '|' && *s ) { // Check if tool starts or ends if ( !strncmp( "tool ", s, 5 ) ) { if ( *tool_active ) { *itemkind = ITEM_LINE_TOOL_END; *tool_active = 0; } else { *itemkind = ITEM_LINE_TOOL_START; *tool_active = 1; } return ( s + 5 ); } // Check if DOT starts or ends if ( !strncmp( "dot ", s, 4 ) ) { if ( *tool_active ) { *itemkind = ITEM_LINE_DOT_END; *tool_active = 0; } else { *itemkind = ITEM_LINE_DOT_START; *tool_active = 1; } return ( s + 4 ); } // Check for DOT file includes else if ( !strncmp( "dotfile ", s, 8 ) && !*tool_active ) { *itemkind = ITEM_LINE_DOT_FILE; return ( s + 8 ); } // Check for exec items else if ( !strncmp( "exec ", s, 5 ) && !*tool_active ) { *itemkind = ITEM_LINE_EXEC; return ( s + 5 ); } } return NULL; } /*****/ /****f* Analyser/RB_Analyse_Document * foo * FUNCTION * Scan all the sourcefiles of all parts of a document for * headers. Store these headers in each part (RB_Part). * SYNOPSIS */ void RB_Analyse_Document( struct RB_Document *arg_document ) /* * INPUTS * o document -- document to be analysed. * RESULT * Each part will contain the headers that were found in the * sourcefile of the part. * SOURCE */ { struct RB_Part *a_part; struct RB_Filename *a_filename; FILE *filehandle; for ( a_part = arg_document->parts; a_part; a_part = a_part->next ) { struct RB_header *new_header = NULL; a_filename = a_part->filename; RB_Say( "analysing %s\n", SAY_DEBUG, Get_Fullname( a_filename ) ); RB_SetCurrentFile( Get_Fullname( a_filename ) ); RB_Header_Lock_Reset( ); filehandle = RB_Open_Source( a_part ); line_number = 0; for ( new_header = Grab_Header( filehandle, arg_document ); new_header; new_header = Grab_Header( filehandle, arg_document ) ) { if ( ToBeAdded( arg_document, new_header ) ) { /* The Add is required before the * Analyse because Add sets the owner of the header * which is needed for error messages. */ RB_Part_Add_Header( a_part, new_header ); Analyse_Items( new_header ); } else { RB_Free_Header( new_header ); } } fclose( filehandle ); } } /*****/ #if 0 /****f* Analyser/Check_Header_Start * FUNCION * Sometimes a user makes a spelling mistake in the name of the first item. * ROBODoc then ignores all the lines leading up to the second item, * (which is the first item ROBODoc recognized). This function * checks for this condition and gives the user a warning. * SYNOPSIS */ static void Check_Header_Start( struct RB_header *arg_header, int first_item ) /* * INPUTS * * arg_header -- the header to be checked. * * first -- the line on which the first item was found * RESULT * A warning is given if the condition occured. * SOURCE */ { if ( first_item ) { int i = 0; for ( i = 0; i < first_item; ++i ) { char *c = arg_header->lines[i]; c = RB_Skip_Whitespace( c ); if ( RB_Has_Remark_Marker( c ) ) { c = RB_Skip_Remark_Marker( c ); for ( ; *c; ++c ) { if ( !utf8_isspace( *c ) ) { RB_Warning_Full( Get_Fullname ( arg_header->owner->filename ), arg_header->line_number, "Header \"%s\" contains text before the fist item, " "this might be caused by a misspelled item name.\n", arg_header->name ); return; } } } } } } /*******/ #endif /****f* Analyser/Is_Empty_Line * FUNCTION * Check if line is empty. This assumes that * Copy_Lines_To_Item has been run on the item. * SYNOPSIS */ static int Is_Empty_Line( char *line ) /* * INPUTS * * line -- the string to be analysed. * SOURCE */ { line = RB_Skip_Whitespace( line ); return ( *line == '\0' ); } /******/ /****f* Analyser/Get_Indent * FUNCION * Determine the indentation of a line by counting * the number of spaces at the begining of the line. * SYNOPSIS */ static int Get_Indent( char *line ) /* * INPUTS * * line -- the line * RESULT * The indentation. * SOURCE */ { int i; for ( i = 0; line[i] && utf8_isspace( line[i] ); ++i ) { /* empty */ } return i; } /*****/ /****f* Analyser/Is_ListItem_Start * FUNCTION * Test if a line starts with something that indicates a list item. * List items start with '*', '-', or 'o'. * SYNPOPSIS */ static int Is_ListItem_Start( char *arg_line, int arg_indent ) /* * INPUTS * * line -- the line * RESULT * * TRUE -- it did start with one of those characters * * FALSE -- it did not. * SOURCE */ { char *c = arg_line; int cur_indent = Get_Indent( arg_line ); if ( cur_indent == arg_indent ) { /* TODO Think there is a function for this */ for ( ; *c && utf8_isspace( *c ); ++c ) { /* empty */ }; if ( *c && ( strlen( c ) >= 3 ) ) { if ( strchr( "*-o", *c ) && utf8_isspace( *( c + 1 ) ) ) { return TRUE; } } } else { /* The line is indented so it must be * the start of a pre block */ } return FALSE; } /*****/ /****f* Analyser/Is_List_Item_Continuation * FUNCTION * Is it like the second line in something like: * * this is a list item * that continues * SYNPOPSIS */ static int Is_List_Item_Continuation( char *arg_line, int indent ) /* * INPUTS * * arg_line -- the current line * * indent -- the indent of the current item. * RESULT * * TRUE -- it is. * * FALSE -- it is not. * SOURCE */ { int this_indent = Get_Indent( arg_line ); return ( this_indent > indent ); } /*****/ #if 0 /****f* Analyser/Is_End_of_List * FUNCTION * Check... (TODO) * INPUTS * * arg_line -- * * indent -- * SOURCE */ static int Is_End_of_List( char *arg_line, int indent ) { int this_indent = Get_Indent( arg_line ); return ( this_indent <= indent ); } /******/ #endif /****f* Analyser/Is_Start_List * FUNCTION * Check... (TODO) * INPUTS * * arg_line -- * * indent -- * SOURCE */ static int Is_Start_List( char *arg_line, int indent ) { int this_indent = Get_Indent( arg_line ); char *c = strrchr( arg_line, ':' ); if ( ( this_indent == indent ) && c ) { for ( ++c; *c; ++c ) { if ( !utf8_isspace( *c ) ) { return FALSE; } } return TRUE; } return FALSE; } /*******/ /****f* Analyser/Remove_List_Char * FUNCTION * Remove... (TODO) * INPUTS * * arg_item -- the item to be analysed. * * start_index -- * SOURCE */ static void Remove_List_Char( struct RB_Item *arg_item, int start_index ) { char *c = arg_item->lines[start_index]->line; for ( ; *c && utf8_isspace( *c ); ++c ) { /* empty */ }; if ( *c && ( strlen( c ) >= 3 ) ) { if ( strchr( "*-o", *c ) && utf8_isspace( *( c + 1 ) ) ) { char *temp = arg_item->lines[start_index]->line; *c = ' '; arg_item->lines[start_index]->line = RB_StrDup( temp + 2 ); free( temp ); } } } /*******/ /****f* Analyser/Analyse_ListBody * FUNCTION * Analyse... (TODO) * SYNPOPSIS */ static int Analyse_ListBody( struct RB_Item *arg_item, int start_index, int arg_indent ) /* * INPUTS * * arg_item -- the item to be analysed. * * start_index -- * * arg_index -- * SOURCE */ { int i = start_index; for ( ; i < arg_item->no_lines; i++ ) { char *line = arg_item->lines[i]->line; if ( ( arg_item->lines[i]->kind == ITEM_LINE_PLAIN ) || ( arg_item->lines[i]->kind == ITEM_LINE_END ) ) { if ( Is_ListItem_Start( line, arg_indent ) ) { arg_item->lines[i]->format |= RBILA_END_LIST_ITEM; arg_item->lines[i]->format |= RBILA_BEGIN_LIST_ITEM; Remove_List_Char( arg_item, i ); } else if ( Is_List_Item_Continuation( line, arg_indent ) ) { /* Nothing */ } else { /* Must be the end of the list */ arg_item->lines[i]->format |= RBILA_END_LIST_ITEM; arg_item->lines[i]->format |= RBILA_END_LIST; break; } } } return i; } /*******/ /****f* Analyser/Analyse_List * FUNCTION * Parse the item text to see if there are any lists. * A list is either case I: * ITEMNAME * o bla bla * o bla bla * or case II: * some text: <-- begin of a list * o bla bla <-- list item * bla bla bla <-- continuation of list item. * o bla bla <-- list item * <-- end of a list * bla bla <-- this can also be the end of a list. * SYNPOPSIS */ static void Analyse_List( struct RB_Item *arg_item, int indent ) /* * INPUTS * * arg_item -- the item to be parsed. * * indent -- the indent of this item. * OUTPUT * * arg_item -- the itemlines will contain formatting information * for all the lists that were found. * SOURCE */ { if ( arg_item->no_lines >= 1 ) { int i = 0; char *line = arg_item->lines[i]->line; /* Case I */ if ( ( arg_item->lines[i]->kind == ITEM_LINE_PLAIN ) && Is_ListItem_Start( line, indent ) ) { /* Case I, the is a list item right after the item name */ arg_item->lines[i]->format |= RBILA_BEGIN_LIST; arg_item->lines[i]->format |= RBILA_BEGIN_LIST_ITEM; Remove_List_Char( arg_item, i ); /* Now try to find the end of the list */ i = Analyse_ListBody( arg_item, 1, indent ); } /* Now search for case II cases */ for ( ; i < arg_item->no_lines; i++ ) { line = arg_item->lines[i]->line; if ( ( arg_item->lines[i]->kind == ITEM_LINE_PLAIN ) && Is_Start_List( line, indent ) ) { ++i; if ( i < arg_item->no_lines ) { line = arg_item->lines[i]->line; if ( ( arg_item->lines[i]->kind == ITEM_LINE_PLAIN ) && Is_ListItem_Start( line, indent ) ) { arg_item->lines[i]->format |= RBILA_BEGIN_LIST; arg_item->lines[i]->format |= RBILA_BEGIN_LIST_ITEM; Remove_List_Char( arg_item, i ); ++i; i = Analyse_ListBody( arg_item, i, indent ); /* One list might be immediately followed * by another. In this case we have to * analyse the last line again. */ line = arg_item->lines[i]->line; if ( ( arg_item->lines[i]->kind == ITEM_LINE_PLAIN ) && Is_Start_List( line, indent ) ) { --i; } } } } } } } /******/ // If (unused(this_function)) { delete(this_function) } #if 0 /****f* Analyser/Dump_Item * FUNCTION * Print debug information. */ static void Dump_Item( struct RB_Item *arg_item ) /* * INPUTS * * arg_item -- the item to be pretty-printed. * SOURCE */ { int i; /* preformatted blocks */ for ( i = 0; i < arg_item->no_lines; i++ ) { char *line = arg_item->lines[i]->line; int format = arg_item->lines[i]->format; printf( "%04d ", i ); printf( ( format & RBILA_END_PARAGRAPH ) ? "p" : " " ); printf( ( format & RBILA_BEGIN_PARAGRAPH ) ? "P" : " " ); printf( ( format & RBILA_END_PRE ) ? "e" : " " ); printf( ( format & RBILA_BEGIN_PRE ) ? "E" : " " ); printf( ( format & RBILA_END_LIST ) ? "l" : " " ); printf( ( format & RBILA_BEGIN_LIST ) ? "L" : " " ); printf( ( format & RBILA_END_LIST_ITEM ) ? "i" : " " ); printf( ( format & RBILA_BEGIN_LIST_ITEM ) ? "I" : " " ); printf( " (%s)\n", line ); } } /*******/ #endif /****f* Analyser/Preformat_All * FUNCTION * Process... (TODO) * SYNPOPSIS */ static void Preformat_All( struct RB_Item *arg_item, int source ) /* * INPUTS * * arg_item -- the item to be pre-formatted. * * source -- is it a source item ? * SOURCE */ { int i; int preformatted = FALSE; char *line = NULL; if ( arg_item->no_lines > 0 ) { i = 0; /* Skip any pipe stuff */ for ( ; ( i < arg_item->no_lines ) && ( arg_item->lines[i]->kind == ITEM_LINE_PIPE ); ++i ) { /* Empty */ } line = arg_item->lines[i]->line; if ( ( arg_item->lines[i]->kind == ITEM_LINE_RAW ) || ( arg_item->lines[i]->kind == ITEM_LINE_PLAIN ) ) { arg_item->lines[i]->format |= RBILA_BEGIN_PRE | ( source ? RBILA_BEGIN_SOURCE : 0 ); preformatted = TRUE; for ( ++i; i < arg_item->no_lines; i++ ) { if ( arg_item->lines[i]->kind == ITEM_LINE_PIPE ) { /* Temporarily end the preformatting to allow * the piping to happen */ arg_item->lines[i]->format |= RBILA_END_PRE | ( source ? RBILA_END_SOURCE : 0 ); /* Find the end of the pipe stuff */ for ( ; ( i < arg_item->no_lines ) && ( arg_item->lines[i]->kind == ITEM_LINE_PIPE ); ++i ) { /* Empty */ }; /* Every item ends with an ITEM_LINE_END, so: */ assert( i < arg_item->no_lines ); /* And re-enable preformatting */ arg_item->lines[i]->format |= RBILA_BEGIN_PRE | ( source ? RBILA_BEGIN_SOURCE : 0 ); } if ( arg_item->lines[i]->kind == ITEM_LINE_END ) { /* If the last line ends with a begin_pre remove * it, otherwise a begin and end pre will be * generated, in the wrong order, on the same line in the output. */ if ( arg_item->lines[i]->format & RBILA_BEGIN_PRE ) { arg_item->lines[i]->format &= ~( RBILA_BEGIN_PRE ); } else { arg_item->lines[i]->format |= RBILA_END_PRE; } arg_item->lines[i]->format |= ( source ? RBILA_END_SOURCE : 0 ); } } } } } /******/ /* * aaaaa * aa *

* aa

* aaa * aaaa * * */ /****f* Analyser/Analyse_Preformatted * FUNCTION * Analyse preformatted text ... (TODO) * SYNPOPSIS */ static void Analyse_Preformatted( struct RB_Item *arg_item, int indent ) /* * INPUTS * * arg_item -- the item to be analysed. * * indent -- * SOURCE */ { int i; int in_list = FALSE; int new_indent = -1; int preformatted = FALSE; char *line = NULL; /* preformatted blocks */ if ( arg_item->no_lines > 0 ) { i = 0; /* Skip any pipe stuff */ for ( ; ( i < arg_item->no_lines ) && ( arg_item->lines[i]->kind == ITEM_LINE_PIPE ); ++i ) { /* Empty */ } line = arg_item->lines[i]->line; if ( ( !in_list ) && ( arg_item->lines[i]->format & RBILA_BEGIN_LIST ) ) { in_list = TRUE; } if ( ( in_list ) && ( arg_item->lines[i]->format & RBILA_END_LIST ) ) { in_list = FALSE; } for ( ++i; i < arg_item->no_lines; i++ ) { if ( arg_item->lines[i]->kind == ITEM_LINE_PIPE ) { if ( preformatted ) { arg_item->lines[i]->format |= RBILA_END_PRE; } for ( ; ( i < arg_item->no_lines ) && ( arg_item->lines[i]->kind == ITEM_LINE_PIPE ); ++i ) { /* Empty */ }; /* Every item ends with an ITEM_LINE_END, so: */ assert( i < arg_item->no_lines ); if ( preformatted ) { arg_item->lines[i]->format |= RBILA_BEGIN_PRE; } } line = arg_item->lines[i]->line; new_indent = Get_Indent( line ); if ( ( !in_list ) && ( arg_item->lines[i]->format & RBILA_BEGIN_LIST ) ) { in_list = TRUE; } if ( ( in_list ) && ( arg_item->lines[i]->format & RBILA_END_LIST ) ) { in_list = FALSE; } if ( !in_list ) { if ( ( new_indent > indent ) && !preformatted ) { preformatted = TRUE; arg_item->lines[i]->format |= RBILA_BEGIN_PRE; } else if ( ( new_indent <= indent ) && preformatted ) { preformatted = FALSE; arg_item->lines[i]->format |= RBILA_END_PRE; } else { /* An empty line */ } } else { /* We are in a list, do nothing */ } } } } /******/ /****f* Analyser/Analyse_Paragraphs * FUNCTION * Analyse paragraphs... (TODO) * SYNPOPSIS */ static void Analyse_Paragraphs( struct RB_Item *arg_item ) /* * INPUTS * * arg_item -- the item to be analysed. * SOURCE */ { int i; int in_par = FALSE; int in_list = FALSE; int in_pre = FALSE; int is_empty = FALSE; int prev_is_empty = FALSE; for ( i = 0; ( i < arg_item->no_lines ) && ( arg_item->lines[i]->kind == ITEM_LINE_PIPE ); ++i ) { /* Empty */ } assert( i < arg_item->no_lines ); if ( ( arg_item->lines[i]->format == 0 ) ) { arg_item->lines[i]->format |= RBILA_BEGIN_PARAGRAPH; in_par = TRUE; } for ( ; i < arg_item->no_lines; i++ ) { char *line = arg_item->lines[i]->line; prev_is_empty = is_empty; is_empty = Is_Empty_Line( line ); if ( arg_item->lines[i]->format & RBILA_BEGIN_LIST ) { in_list = TRUE; } if ( arg_item->lines[i]->format & RBILA_BEGIN_PRE ) { in_pre = TRUE; } if ( arg_item->lines[i]->format & RBILA_END_LIST ) { in_list = FALSE; } if ( arg_item->lines[i]->format & RBILA_END_PRE ) { in_pre = FALSE; } if ( in_par ) { if ( ( arg_item->lines[i]->format & RBILA_BEGIN_LIST ) || ( arg_item->lines[i]->format & RBILA_BEGIN_PRE ) || is_empty ) { in_par = FALSE; arg_item->lines[i]->format |= RBILA_END_PARAGRAPH; } } else { if ( ( arg_item->lines[i]->format & RBILA_END_LIST ) || ( arg_item->lines[i]->format & RBILA_END_PRE ) || ( !is_empty && prev_is_empty && !in_list && !in_pre ) ) { in_par = TRUE; arg_item->lines[i]->format |= RBILA_BEGIN_PARAGRAPH; } } } if ( in_par ) { arg_item->lines[arg_item->no_lines - 1]->format |= RBILA_END_PARAGRAPH; } } /******/ /****f* Analyser/Analyse_Indentation * FUNCTION * Figure out how text is indented. * SYNPOPSIS */ static int Analyse_Indentation( struct RB_Item *arg_item ) /* * INPUTS * * arg_item -- the item to be analysed. * SOURCE */ { int i; int indent = -1; assert( arg_item->no_lines > 0 ); for ( i = 0; i < arg_item->no_lines; ++i ) { if ( arg_item->lines[i]->kind == ITEM_LINE_PLAIN ) { char *line = arg_item->lines[i]->line; if ( Is_Empty_Line( line ) ) { /* Empty */ indent = 0; } else { indent = Get_Indent( line ); break; } } } assert( indent >= 0 ); return indent; } /******/ /****f* Analyser/Analyse_Item_Format * FUNCTION * Try to determine the formatting of an item. * An empty line generates a new paragraph * Things that are indented more that the rest of the text * are preformatted. * Things that start with a '*' or '-' create list items * unless they are indented more that the rest of the text. * SYNPOPSIS */ static void Analyse_Item_Format( struct RB_Item *arg_item ) /* * INPUTS * * arg_item -- the item to be analysed. * SOURCE */ { // If item is not empty if ( arg_item->no_lines ) { // If it is a SOURCE item if ( Works_Like_SourceItem( arg_item->type ) ) { // Preformat it like a SOURCE item Preformat_All( arg_item, TRUE ); } // Check if we have to analyse this item else if ( ( course_of_action.do_nopre || Is_Format_Item( arg_item->type ) ) && !Is_Preformatted_Item( arg_item->type ) ) { // analyse item int indent = Analyse_Indentation( arg_item ); Analyse_List( arg_item, indent ); Analyse_Preformatted( arg_item, indent ); Analyse_Paragraphs( arg_item ); } // If none of above, preformat item else { // Preformat it Preformat_All( arg_item, FALSE ); } } // Item is empty else { // Do nothing } } /*****/ static int Trim_Empty_Item_Begin_Lines( struct RB_header *arg_header, struct RB_Item *arg_item, int current_index ) { char *c; if ( Works_Like_SourceItem( arg_item->type ) ) { /* We skip the first line after the source item, if * it an remark end marker -- such as '*)' */ c = arg_header->lines[current_index]; if ( RB_Is_Remark_End_Marker( c ) ) { c = RB_Skip_Remark_End_Marker( c ); c = RB_Skip_Whitespace( c ); if ( *c != '\0' ) { c = arg_header->lines[current_index]; RB_Warning( "text following a remark end marker:\n%s\n", c ); } ++current_index; } } if ( current_index > arg_item->end_index ) { /* item is empty */ } else { do { c = arg_header->lines[current_index]; c = RB_Skip_Whitespace( c ); if ( RB_Has_Remark_Marker( c ) ) { c = RB_Skip_Remark_Marker( c ); } c = RB_Skip_Whitespace( c ); if ( *c == '\0' ) { ++current_index; } } while ( ( *c == '\0' ) && ( current_index < arg_item->end_index ) ); } return current_index; } static int Trim_Empty_Item_End_Lines( struct RB_header *arg_header, struct RB_Item *arg_item, int current_index ) { char *c; if ( Works_Like_SourceItem( arg_item->type ) ) { c = arg_header->lines[current_index]; if ( RB_Is_Remark_Begin_Marker( c ) ) { c = RB_Skip_Remark_Begin_Marker( c ); c = RB_Skip_Whitespace( c ); if ( *c != '\0' ) { c = arg_header->lines[current_index]; RB_Warning( "text following a remark begin marker:\n%s\n", c ); } --current_index; } } do { c = arg_header->lines[current_index]; c = RB_Skip_Whitespace( c ); if ( RB_Has_Remark_Marker( c ) ) { c = RB_Skip_Remark_Marker( c ); } c = RB_Skip_Whitespace( c ); if ( *c == '\0' ) { --current_index; } } while ( ( *c == '\0' ) && ( current_index > arg_item->begin_index ) ); return current_index; } static void Trim_Empty_Item_Lines( struct RB_header *arg_header, struct RB_Item *arg_item ) { arg_item->no_lines = arg_item->end_index - arg_item->begin_index + 1; if ( arg_item->no_lines <= 1 ) { /* item is empty */ arg_item->no_lines = 0; } else { int current_index; /* trim all empty lines at the begin of an item */ /* we skip the first line because that contains the item name. */ current_index = arg_item->begin_index + 1; current_index = Trim_Empty_Item_Begin_Lines( arg_header, arg_item, current_index ); /* Is there anything left? */ if ( current_index <= arg_item->end_index ) { arg_item->begin_index = current_index; /* trim all the empty lines at the end of an item */ current_index = arg_item->end_index; current_index = Trim_Empty_Item_End_Lines( arg_header, arg_item, current_index ); if ( current_index >= arg_item->begin_index ) { arg_item->end_index = current_index; arg_item->no_lines = arg_item->end_index - arg_item->begin_index + 1; } else { /* item is empty */ arg_item->no_lines = 0; } } else { /* item is empty */ arg_item->no_lines = 0; } } } /* TODO This routine is way too long */ static void Copy_Lines_To_Item( struct RB_header *arg_header, struct RB_Item *arg_item ) { #if 0 printf( "%d\n%d\n%s\n%s\n", arg_item->begin_index, arg_item->end_index, arg_header->lines[arg_item->begin_index], arg_header->lines[arg_item->end_index] ); #endif Trim_Empty_Item_Lines( arg_header, arg_item ); if ( arg_item->no_lines > 0 ) { int i = 0; int j = 0; struct RB_Item_Line *itemline = NULL; int tool_active = 0; // Shows wether we are inside a tool body /* Allocate enough memory for all the lines, plus one * extra line */ ++arg_item->no_lines; arg_item->lines = calloc( arg_item->no_lines, sizeof( struct RB_Item_Line * ) ); if ( !arg_item->lines ) { RB_Panic( "Out of memory! %s\n", "Copy_Lines_To_Item" ); } /* And create an RB_Item_Line for each of them, and add * those to the RB_Item */ for ( i = 0; i < arg_item->no_lines - 1; ++i ) { char *c = arg_header->lines[arg_item->begin_index + i]; /* TODO should be a Create_ItemLine() */ itemline = malloc( sizeof( struct RB_Item_Line ) ); if ( !itemline ) { RB_Panic( "Out of memory! %s (2)\n", "Copy_Lines_To_Item" ); } c = ExpandTab( c ); c = RB_Skip_Whitespace( c ); // Lines with remark marker if ( RB_Has_Remark_Marker( c ) && !Works_Like_SourceItem( arg_item->type ) ) { char *c2, *c3; int pipe_mode; enum ItemLineKind item_kind; c = RB_Skip_Remark_Marker( c ); c2 = RB_Skip_Whitespace( c ); if ( *c2 ) { // Check wether a tool starts or ends if ( ( c3 = Is_Tool( c2, &item_kind, &tool_active ) ) ) { itemline->kind = item_kind; c = c3; } // If we have an active tool, copy the body lines else if ( tool_active ) { itemline->kind = ITEM_LINE_TOOL_BODY; c++; // Skip space after the remark marker } // Check for pipes else if ( ( c3 = Is_Pipe_Marker( c2, &pipe_mode ) ) ) { itemline->kind = ITEM_LINE_PIPE; itemline->pipe_mode = pipe_mode; c = c3; } // Plain Items ... else { itemline->kind = ITEM_LINE_PLAIN; } } // Empty lines with remark markers and active tool else if ( tool_active ) { itemline->kind = ITEM_LINE_TOOL_BODY; } // Plain empty lines with remark markers... else { itemline->kind = ITEM_LINE_PLAIN; } } else { itemline->kind = ITEM_LINE_RAW; /* The is raw code, so we do not want to have the * whitespace stripped of */ c = arg_header->lines[arg_item->begin_index + i]; c = ExpandTab( c ); } if ( ( !Works_Like_SourceItem( arg_item->type ) && ( itemline->kind != ITEM_LINE_RAW ) ) || Works_Like_SourceItem( arg_item->type ) ) { itemline->line = RB_StrDup( c ); itemline->format = 0; arg_item->lines[j] = itemline; ++j; } else { /* We dump the RAW item lines if we are not in a * source item. */ free( itemline ); } } if ( j > 0 ) { /* And one empty line to mark the end of an item and * to be able to store some additional formatting actions */ itemline = malloc( sizeof( struct RB_Item_Line ) ); if ( !itemline ) { RB_Panic( "Out of memory! %s (3)\n", "Copy_Lines_To_Item" ); } itemline->kind = ITEM_LINE_END; itemline->line = RB_StrDup( "" ); itemline->format = 0; arg_item->lines[j] = itemline; /* Store the real number of lines we copied */ assert( arg_item->no_lines >= ( j + 1 ) ); arg_item->no_lines = j + 1; } else { arg_item->no_lines = 0; free( arg_item->lines ); arg_item->lines = NULL; } } else { arg_item->no_lines = 0; arg_item->lines = NULL; } } /****f* Analyser/RB_Analyse_Items * FUNCTION * Locate the items in the header and create RB_Item structures for * them. * SYNPOPSIS */ static int Analyse_Items( struct RB_header *arg_header ) /* * SOURCE */ { int line_nr; enum ItemType item_type = NO_ITEM; struct RB_Item *new_item; struct RB_Item *cur_item; RB_Item_Lock_Reset( ); /* find the first item */ for ( line_nr = 0; line_nr < arg_header->no_lines; ++line_nr ) { item_type = RB_Is_ItemName( arg_header->lines[line_nr] ); if ( item_type != NO_ITEM ) { break; } } /* and all the others */ while ( ( item_type != NO_ITEM ) && ( line_nr < arg_header->no_lines ) ) { new_item = RB_Create_Item( item_type ); new_item->begin_index = line_nr; /* Add the item to the end of the list of items. */ if ( arg_header->items ) { for ( cur_item = arg_header->items; cur_item->next; cur_item = cur_item->next ) { /* Empty */ } cur_item->next = new_item; } else { arg_header->items = new_item; } /* Find the next item */ for ( ++line_nr; line_nr < arg_header->no_lines; ++line_nr ) { item_type = RB_Is_ItemName( arg_header->lines[line_nr] ); if ( item_type != NO_ITEM ) { break; } } /* This points to the last line in the item */ new_item->end_index = line_nr - 1; assert( new_item->end_index >= new_item->begin_index ); /* Now analyse and copy the lines */ Copy_Lines_To_Item( arg_header, new_item ); Analyse_Item_Format( new_item ); /* Handy for debugging wiki formatting * Dump_Item( new_item ); */ } return 0; } /******/ /****f* Analyser/ToBeAdded * FUNCTION * Test whether or not a header needs to be added to the * list of headers. This implements the options * --internal * and * --internalonly * SYNPOPSIS */ static int ToBeAdded( struct RB_Document *document, struct RB_header *header ) /* * INPUTS * o document -- a document (to determine the options) * o header -- a header * RESULT * TRUE -- Add header * FALSE -- Don't add header * SOURCE */ { int add = FALSE; if ( header->is_internal ) { if ( ( document->actions.do_include_internal ) || ( document->actions.do_internal_only ) ) { add = TRUE; } else { add = FALSE; } } else { if ( document->actions.do_internal_only ) { add = FALSE; } else { add = TRUE; } } return add; } /******/ /****f* Analyser/Grab_Header * FUNCTION * Grab a header from a source file, that is scan a source file * until the start of a header is found. Then search for the end * of a header and store all the lines in between. * SYNPOPSIS */ static struct RB_header *Grab_Header( FILE *sourcehandle, struct RB_Document *arg_document ) /* * INPUTS * o sourcehandle -- an opened source file. * OUTPUT * o sourcehandle -- will point to the line following the end marker. * RESULT * 0 if no header was found, or a pointer to a new header otherwise. * SOURCE */ { struct RB_header *new_header = NULL; int is_internal = 0; struct RB_HeaderType *header_type = NULL; int good_header = FALSE; int reuse = FALSE; do { good_header = FALSE; header_type = RB_Find_Marker( sourcehandle, &is_internal, reuse ); reuse = FALSE; if ( header_type ) { struct RB_header *duplicate_header = NULL; long previous_line = 0; new_header = RB_Alloc_Header( ); new_header->htype = header_type; new_header->is_internal = is_internal; if ( Find_Header_Name( sourcehandle, new_header ) ) { new_header->line_number = line_number; RB_Say( "found header [line %5d]: \"%s\"\n", SAY_DEBUG, line_number, new_header->name ); duplicate_header = RB_Document_Check_For_Duplicate( arg_document, new_header ); if ( duplicate_header ) { /* Duplicate headers do not crash the program so * we accept them. But we do warn the user. */ RB_Warning ( "A header with the name \"%s\" already exists.\n See %s(%d)\n", new_header->name, Get_Fullname( duplicate_header->owner->filename ), duplicate_header->line_number ); } if ( ( new_header->function_name = Function_Name( new_header->name ) ) == NULL ) { RB_Warning( "Can't determine the \"function\" name.\n" ); RB_Free_Header( new_header ); new_header = NULL; } else { if ( ( new_header->module_name = Module_Name( new_header->name ) ) == NULL ) { RB_Warning ( "Can't determine the \"module\" name.\n" ); RB_Free_Header( new_header ); new_header = NULL; } else { previous_line = line_number; if ( Find_End_Marker( sourcehandle, new_header ) == 0 ) { RB_Warning ( "found header on line %d with name \"%s\"\n" " but I can't find the end marker\n", previous_line, new_header->name ); /* Reuse the current line while finding the next * Marking using RB_Find_Marker() */ reuse = TRUE; RB_Free_Header( new_header ); new_header = NULL; } else { RB_Say( "found end header [line %5d]:\n", SAY_DEBUG, line_number ); /* Good header found, we can stop */ good_header = TRUE; } } } } else { RB_Warning( "found header marker but no name\n" ); RB_Free_Header( new_header ); new_header = NULL; } } else { /* end of the file */ good_header = TRUE; } } while ( !good_header ); return new_header; } /*******/ /****f* Analyser/Module_Name * FUNCTION * Get the module name from the header name. The header name will be * something like * * module/functionname. * * SYNPOPSIS */ static char *Module_Name( char *header_name ) /* * INPUTS * o header_name -- a pointer to a nul terminated string. * RESULT * Pointer to the modulename. You're responsible for freeing it. * SEE ALSO * Function_Name() * SOURCE */ { char *cur_char; char c; char *name = NULL; assert( header_name ); for ( cur_char = header_name; *cur_char && *cur_char != '/'; ++cur_char ); if ( *cur_char ) { c = *cur_char; *cur_char = '\0'; name = RB_StrDup( header_name ); *cur_char = c; } return name; } /******/ /****f* Analyser/Function_Name * FUNCTION * A header name is consists of two parts. The module name and * the function name. This returns a pointer to the function name. * The name "function name" is a bit obsolete. It is really the name * of any of objects that can be documented; classes, methods, * variables, functions, projects, etc. * SYNOPSIS */ static char *Function_Name( char *header_name ) /* * SOURCE */ { char *cur_char; char *name; name = NULL; if ( ( cur_char = header_name ) != NULL ) { for ( ; *cur_char != '\0'; ++cur_char ) { if ( '/' == *cur_char ) { ++cur_char; if ( *cur_char ) { name = cur_char; break; } } } } if ( name ) { return RB_StrDup( name ); } else { return ( name ); } } /*** Function_Name ***/ /****f* Analyser/RB_Find_Marker * NAME * RB_Find_Marker -- Search for header marker in document. * FUNCTION * Read document file line by line, and search each line for * any of the headers defined in the array header_markers (OR * if using the -rh switch, robo_head) * SYNOPSIS */ static struct RB_HeaderType *RB_Find_Marker( FILE *document, int *is_internal, int reuse_previous_line ) /* * INPUTS * document - pointer to the file to be searched. * the gobal buffer line_buffer. * OUTPUT * o document will point to the line after the line with * the header marker. * o is_internal will be TRUE if the header is an internal * header. * RESULT * o header type * BUGS * Bad use of feof(), fgets(). * SEE ALSO * Find_End_Marker * SOURCE */ { int found; char *cur_char; struct RB_HeaderType *header_type = 0; cur_char = NULL; found = FALSE; while ( !feof( document ) && !found ) { if ( reuse_previous_line ) { /* reuse line in the line_buffer */ reuse_previous_line = FALSE; } else { RB_FreeLineBuffer( ); myLine = RB_ReadWholeLine( document, line_buffer, &readChars ); } if ( !feof( document ) ) { line_number++; found = RB_Is_Begin_Marker( myLine, &cur_char ); if ( found ) { header_type = AnalyseHeaderType( &cur_char, is_internal ); RB_Say( "found header marker of type %s\n", SAY_DEBUG, header_type->indexName ); } } } return header_type; } /******** END RB_Find_Marker ******/ /****f* Analyser/AnalyseHeaderType * FUNCTION * Determine the type of the header. * SYNOPSIS */ struct RB_HeaderType *AnalyseHeaderType( char **cur_char, int *is_internal ) /* * INPUTS * o cur_char -- pointer to the header type character * OUTPUT * o is_internal -- indicates if it is an internal header or not.* * o cur_char -- points to the header type character * RESULT * o pointer to a RB_HeaderType * SOURCE */ { struct RB_HeaderType *headertype = 0; *is_internal = RB_IsInternalHeader( **cur_char ); if ( *is_internal ) { /* Skip the character */ ++( *cur_char ); } headertype = RB_FindHeaderType( **cur_char ); if ( !headertype ) { RB_Panic( "Undefined headertype (%c)\n", **cur_char ); } return headertype; } /*******/ /****f* Analyser/Find_End_Marker * FUNCTION * Scan and store all lines from a source file until * an end marker is found. * SYNOPSIS */ static int Find_End_Marker( FILE *document, struct RB_header *new_header ) /* * INPUTS * o document -- a pointer to an opened source file. * OUTPUT * o new_header -- the lines of source code will be added * here. * RESULT * o TRUE -- an end marker was found. * o FALSE -- no end marker was found while scanning the * source file. * SOURCE */ { int found = FALSE; unsigned int no_lines = 0; unsigned int max_no_lines = 10; char **lines = NULL; char **new_lines = NULL; char *dummy; lines = ( char ** ) calloc( max_no_lines, sizeof( char * ) ); if ( !lines ) { RB_Panic( "Out of memory! %s()\n", "Find_End_Marker" ); } while ( !feof( document ) ) { RB_FreeLineBuffer( ); myLine = RB_ReadWholeLine( document, line_buffer, &readChars ); ++line_number; /* global linecounter, koessi */ if ( RB_Is_Begin_Marker( myLine, &dummy ) ) { /* Bad... found a begin marker but was expecting to find an end marker. Panic... */ found = FALSE; return found; } else if ( RB_Is_End_Marker( myLine ) ) { RB_Say( "Found end marker \"%s\"", SAY_DEBUG, myLine ); found = TRUE; break; } else { unsigned int n; char *line; line = RB_StrDup( myLine ); n = strlen( line ); assert( n > 0 ); assert( line[n - 1] == '\n' ); /* Strip CR */ line[n - 1] = '\0'; lines[no_lines] = line; ++no_lines; if ( no_lines == max_no_lines ) { max_no_lines *= 2; new_lines = realloc( lines, max_no_lines * sizeof( char * ) ); if ( !new_lines ) { RB_Panic( "Out of memory! %s()\n", "Find_End_Marker" ); } lines = new_lines; } } } new_header->no_lines = no_lines; new_header->lines = lines; return found; } /******/ /* TODO Documentation */ static void Remove_Trailing_Asterics( char *line ) { int i = strlen( line ) - 1; for ( ; ( i > 0 ) && utf8_isspace( line[i] ); i-- ) { /* Empty */ } for ( ; ( i > 0 ) && ( line[i] == '*' ); i-- ) { line[i] = ' '; } } /****if* Utilities/RB_WordWithSpacesLen * SYNOPSIS */ static int RB_WordWithSpacesLen( char *str ) /* * FUNCTION * get the amount of bytes until next separator character or ignore character * or end of line * INPUTS * char *str -- the line * RESULT * int -- length of the next word or 0 * SEE ALSO * RB_Find_Header_Name() * SOURCE */ { int len; char c; for ( len = 0; ( ( c = *str ) != '\0' ) && ( c != '\n' ); ++str, ++len ) { // Look for header truncating characters if ( Find_Parameter_Char( &( configuration.header_separate_chars ), c ) != NULL || Find_Parameter_Char( &( configuration.header_ignore_chars ), c ) != NULL ) { // and exit loop if any found break; } } /* Don't count any of the trailing spaces. */ if ( len ) { --str; for ( ; utf8_isspace( *str ); --len, --str ) { /* empty */ } } return ( len ); } /*** RB_WordWithSpacesLen ***/ /* TODO Documentation */ static int Find_Header_Name( FILE *fh, struct RB_header *hdr ) { char *cur_char = myLine; char **names = NULL; int num = 0; Remove_Trailing_Asterics( cur_char ); skip_while( *cur_char != '*' ); skip_while( !utf8_isspace( *cur_char ) ); skip_while( utf8_isspace( *cur_char ) ); while ( *cur_char ) { int length = RB_WordWithSpacesLen( cur_char ); if ( length == 0 ) break; names = realloc( names, ( ++num ) * sizeof *names ); if ( !names ) { RB_Panic( "Out of memory! %s()\n", "Find_Header_Name" ); } names[num - 1] = RB_StrDupLen( cur_char, length ); /* printf("%c adding name = %s\n", num > 1 ? ' ' : '*', names[ num - 1 ] ); */ cur_char += length; if ( Find_Parameter_Char( &( configuration.header_separate_chars ), *cur_char ) ) { for ( cur_char++; utf8_isspace( *cur_char ); cur_char++ ); /* End of line reach, but comma encountered, more headernames follow on next line */ if ( *cur_char == 0 ) { /* Skip comment */ RB_FreeLineBuffer( ); myLine = RB_ReadWholeLine( fh, line_buffer, &readChars ); line_number++; for ( cur_char = myLine; *cur_char && !utf8_isalpha( *cur_char ); cur_char++ ); } } } hdr->names = names; hdr->no_names = num; hdr->name = num ? names[0] : NULL; return num; } /***** Find_Header_Name *****/