home *** CD-ROM | disk | FTP | other *** search
/ The C Users' Group Library 1994 August / wc-cdrom-cusersgrouplibrary-1994-08.iso / vol_300 / 329_01 / cflow.c < prev    next >
C/C++ Source or Header  |  1989-10-18  |  17KB  |  706 lines

  1. /*  >  cFlow.c
  2.  *
  3.  *  cFlow -- Print function dependency tree
  4.  *           from multiple C source files
  5.  *  (C) October 18  1989  Asaf Arkin
  6.  *  All rights reserved
  7.  */
  8.  
  9.  
  10.  
  11. /*  Include files:
  12.  */
  13.  
  14. #include  <ctype.h>
  15. #include  <stdarg.h>
  16. #include  <stdio.h>
  17. #include  <stdlib.h>
  18. #include  <string.h>
  19.  
  20.  
  21. /*  Macro constants:
  22.  *    True/False; function state mask bits; constant values.
  23.  */
  24.  
  25. #define  TRUE  1
  26. #define  FALSE  0
  27.  
  28. #define  DEFINED  01    /*  State: function defined          */
  29. #define  PRINTED  02    /*  State: function already printed  */
  30.  
  31. #define  ID_MAX  512    /*  Maximum identifier length  */
  32. #define  IND_ADD  2     /*  Indentation increment      */
  33.  
  34.  
  35. /*  Function declarations:
  36.  */
  37.  
  38. int   Error(char *, ...);
  39. void  Syntax(void);
  40. void  Process(char *);
  41. void  SkipQuote(FILE *, int *, int);
  42. void   DoFunc(int, FILE *, int *, int);
  43. void  *ScanFunc(char *);
  44. int   GetChar(FILE *, int *);
  45. void  DumpFlow(char *);
  46. void  DumpFunc(void *, int, int);
  47.  
  48.  
  49. /*  Structure strFunc: holds a function's state, line number, filename
  50.  *  offset, pointer to definition text, and offsets to invoked functions.
  51.  */
  52.  
  53. typedef struct
  54. {
  55.   char      State;
  56.   int       LineNumber;
  57.   unsigned  FileName;
  58.   char     *Definition;
  59.   unsigned  CallCnt;
  60.   unsigned  Calls;
  61. }  strFunc;
  62.  
  63.  
  64. /*  Structure strList: holds an offset to function's name and a pointer to
  65.  *  function's strFunc structure.
  66.  */
  67.  
  68. typedef struct
  69. {
  70.   int       Name;
  71.   strFunc  *Struct;
  72. }  strList;
  73.  
  74.  
  75. /*  Static variables:
  76.  *    Memory blocks: ID, File, Name, List.
  77.  *    Pointer to Func block of currently defined function.
  78.  *    Undefined (options -u) and include (option -i) flags.
  79.  *    cFlow output stream.
  80.  */
  81.  
  82. char     *IDName;
  83. int       IDLen;
  84. char     *FileBase = NULL;
  85. int       FileLen = 0;
  86. char     *NameBase = NULL;
  87. int       NameLen = 0;
  88. strList  *ListBase = NULL;
  89. int       ListLen = 0;
  90. strFunc  *CurFunc = NULL;
  91. char      UnDefined = 0,  Include = FALSE;
  92. FILE     *OutFile = stdout;
  93.  
  94.  
  95.  
  96. /*  int  main(int, char **)
  97.  *
  98.  *  Parse command line off options. That done, consume filenames from command
  99.  *  line, processing them with Process(). Finally, print dependency tree.
  100.  */
  101.  
  102. int  main(int Argc, char **Argv)
  103. {
  104.   int  Arg,  Offset = 0;
  105.   char  *Ptr;
  106.   char  *MainFunc = "main";
  107.  
  108.   /*  Print cFlow title. Allocate memory for ID (constant size) and quit if
  109.    *  failed to. In case of no command-line arguments, print syntax of cFlow
  110.    *  and quit.
  111.    */
  112.   printf("cFlow   (C) Oct 17 '89  by Asaf Arkin\n\n");
  113.   if (( IDName=malloc(ID_MAX) )==NULL)
  114.     return  Error("Cannot allocate memory to begin with");
  115.   if (Argc<2)
  116.   {
  117.     Syntax();
  118.     return  0;
  119.   }
  120.   for (Arg=1; Arg<Argc; ++Arg)
  121.   {
  122.     Argv[Arg]=Argv[Arg+Offset];
  123.     if (Argv[Arg][0]=='-')
  124.     {
  125.       for (Ptr=Argv[Arg]+1; *Ptr; ++Ptr)
  126.         switch (tolower(*Ptr))
  127.         {
  128.           case 'i':
  129.             Include=TRUE;
  130.             break;
  131.           case 'm':
  132.             if (!*++Ptr)
  133.             {
  134.               if (Arg+1<Argc)
  135.               {
  136.                 --Argc;
  137.                 Ptr=Argv[Arg+ ++Offset];
  138.               }
  139.               else
  140.               {
  141.                 Error("-m<main>  First function in report is <main>\n");
  142.                 break;
  143.               }
  144.             }
  145.             MainFunc=Ptr;
  146.             while (*++Ptr)  ;
  147.             --Ptr;
  148.             break;
  149.           case 'o':
  150.             if (!*++Ptr)
  151.             {
  152.               if (Arg+1<Argc)
  153.               {
  154.                 --Argc;
  155.                 Ptr=Argv[Arg+ ++Offset];
  156.               }
  157.               else
  158.               {
  159.                 Error("-o<file>  Send output to <file>");
  160.                 break;
  161.               }
  162.             }
  163.             if (( OutFile=freopen(Ptr,"w",OutFile) )==NULL)
  164.               return  Error("Cannot open file '%s' for output",Ptr);
  165.             while (*++Ptr)  ;
  166.             --Ptr;
  167.             break;
  168.           case 'u':
  169.             ++UnDefined;
  170.             break;
  171.           case '-':
  172.             break;
  173.           default:
  174.             Error("Option -%c unknown",*Ptr);
  175.         }
  176.       --Argc;
  177.       --Arg;
  178.       ++Offset;
  179.     }
  180.   }
  181.   for (Arg=1; Arg<Argc; ++Arg)
  182.     Process(Argv[Arg]);
  183.   DumpFlow(MainFunc);
  184.   return  0;
  185. }
  186.  
  187.  
  188. /*  int  Error(char *, ...)
  189.  *
  190.  *  Issue an error (printf-style arguments) and return zero.
  191.  */
  192.  
  193. int  Error(char *Message, ...)
  194. {
  195.   va_list  Args;
  196.  
  197.   va_start(Args,Message);
  198.   printf("cFlow: ");
  199.   vprintf(Message,Args);
  200.   printf(".\n");
  201.   va_end(Args);
  202.   return  0;
  203. }
  204.  
  205.  
  206. /*  void  Syntax(void)
  207.  *
  208.  *  Print cFlow's syntax.
  209.  */
  210.  
  211. void  Syntax(void)
  212. {
  213.   char  *Message =
  214.     "Syntax:   cFlow  [<file>|-i|-m<main>|-o<file>|-u|-v]...\n"
  215.     "Options:  -i        Read include files\n"
  216.     "          -m<main>  First function in report is <main>\n"
  217.     "          -o<file>  Send output to <file>\n"
  218.     "          -u        Show undefined functions (2 levels)\n\n";
  219.  
  220.   printf("%s",Message);
  221. }
  222.  
  223.  
  224.  
  225. /*  void  Process(char *)
  226.  *
  227.  *  Parse function definitions and invokations from the given C source file
  228.  *  and build from it the dependency tree.
  229.  */
  230.  
  231. void  Process(char *FileName)
  232. {
  233.   int  c,  LineCnt = 0,  Level = 0;
  234.   char  *Ptr,  *EndPtr;
  235.   FILE  *File;
  236.  
  237.   if (( File=fopen(FileName,"r") )==NULL)
  238.   {
  239.     Error("Cannot open source file '%s'",FileName);
  240.     return;
  241.   }
  242.   /*  If file has not been openned before, append its name to File block,
  243.    *  else quit (no point in reading a file twice.) Set FileName to the
  244.    *  filename's offset within File.
  245.    */
  246.   Ptr=FileBase;
  247.   EndPtr=Ptr+FileLen;
  248.   for (; Ptr<EndPtr; Ptr+=strlen(Ptr)+1)
  249.     if (!strcmp(FileName,Ptr))
  250.       return;
  251.   if (( Ptr=realloc(FileBase, FileLen+strlen(FileName)+1) )!=NULL)
  252.   {
  253.     FileName=strcpy(Ptr+FileLen,FileName);
  254.     FileBase=Ptr;
  255.     FileLen+=strlen(FileName)+1;
  256.   }
  257.   /*  Flush ID and start parsing the source file.
  258.    */
  259.   IDLen=0;
  260.   c=GetChar(File,&LineCnt);
  261.   while (!feof(File))
  262.   {
  263.     if (c==' ')
  264.     {
  265.       /*  Skip spaces, tabs and line delimiters.
  266.        */
  267.       c=GetChar(File,&LineCnt);
  268.       continue;
  269.     }
  270.     if (isalpha(c) || c=='_' || c=='*')
  271.     {
  272.       /*  Identifier: consume as many characters as possible into ID. *'s
  273.        *  are read because they are part of definitions (pointer to..).
  274.        */
  275.       while (c=='*')
  276.       {
  277.         IDName[IDLen++]=c;
  278.         c=GetChar(File,&LineCnt);
  279.       }
  280.       if (isalpha(c) || c=='_')
  281.       {
  282.         do
  283.         {
  284.           IDName[IDLen++]=c;
  285.           c=GetChar(File,&LineCnt);
  286.         }
  287.         while (isalnum(c) || c=='_');
  288.         IDName[IDLen++]=' ';
  289.       }
  290.       continue;
  291.     }
  292.     if (c=='(' && IDLen)
  293.     {
  294.       /*  Openning parentheses: could be a function call/definition.
  295.        */
  296.       DoFunc(FileName-FileBase,File,&LineCnt,Level);
  297.       IDLen=0;
  298.       c=GetChar(File,&LineCnt);
  299.       continue;
  300.     }
  301.     /*  Skip literal strings and character constants.
  302.      *  Count left and right braces to determine nesting level.
  303.      */
  304.     if (c=='\'' || c=='\"')
  305.       SkipQuote(File,&LineCnt,c);
  306.     else
  307.     if (c=='{')
  308.       ++Level;
  309.     else
  310.     if (c=='}')
  311.       if (--Level<0)
  312.         Error("Source error: too many }'s do not balance");
  313.     IDLen=0;
  314.     c=GetChar(File,&LineCnt);
  315.   }
  316.   /*  File parsed through. Close it an return.
  317.    */
  318.   if (fclose(File))
  319.     Error("Source file '%s' not closed",FileName);
  320. }
  321.  
  322.  
  323. /*  void  SkipQuote(FILE *, int *, int)
  324.  *
  325.  *  Skip literal strings ("...") and character constants ('.').
  326.  */
  327.  
  328. void  SkipQuote(FILE *File, int *LineCnt, int c)
  329. {
  330.   int  Quote;
  331.  
  332.   Quote=c;
  333.   do
  334.   {
  335.     c=getc(File);
  336.     if (c=='\n' || c=='\r' || c=='\v' || c=='\f')
  337.       ++*LineCnt;
  338.     else
  339.     if (c=='\\')
  340.     {
  341.       c=getc(File);
  342.       if (c=='\n')
  343.         ++*LineCnt;
  344.       else
  345.         c=getc(File);
  346.     }
  347.   }
  348.   while (c!=Quote && !feof(File));
  349. }
  350.  
  351.  
  352. /*  int  GetChar(FILE *, int *)
  353.  *
  354.  *  Read a character from the source file: whitespaces, line delimiters and
  355.  *  comments read as spaces; \NL sequences are ignored; preprocessor
  356.  *  directives are ignored, except for #include.
  357.  */
  358.  
  359. int  GetChar(FILE *File, int *LineCnt)
  360. {
  361.   int  c,  Next;
  362.   char  FileName[21];
  363.  
  364. GetCharNext:
  365.   switch (c=fgetc(File))
  366.   {
  367.     case ' ':
  368.     case '\t':
  369.       return  ' ';
  370.     case '\n':
  371.     case '\r':
  372.     case '\v':
  373.     case '\f':
  374.       ++*LineCnt;
  375.       while (c=fgetc(File), c==' ' || c=='\t')  ;
  376.       if (c=='#')
  377.       {
  378.         /*  If #include directive and -i option specified, open include file,
  379.          *  and read its contents with Process().
  380.          *  A preprocessor line is skipped in whole.
  381.          */
  382.         while (c=fgetc(File), c==' ' || c=='\t')  ;
  383.         if (Include && c=='i')
  384.           if (fscanf(File, "nclude %*1[<\"] %20[^>\" ]",FileName )==1)
  385.             Process(FileName);
  386.         do
  387.         {
  388.           if (c=='\\' || c=='/')
  389.           {
  390.             ungetc(c,File);
  391.             c=GetChar(File,LineCnt);
  392.           }
  393.           c=fgetc(File);
  394.         }
  395.         while (!feof(File) && c!='\n' && c!='\r' && c!='\v' && c!='\f');
  396.         ungetc(c,File);
  397.         return  ' ';
  398.       }
  399.       ungetc(c,File);
  400.       return ' ';
  401.     case '\\':
  402.       c=fgetc(File);
  403.       if (c=='\n')
  404.       {
  405.         ++*LineCnt;
  406.         goto  GetCharNext;
  407.       }
  408.       ungetc(c,File);
  409.       return  '\\';
  410.     case '/':
  411.       c=fgetc(File);
  412.       if (c=='*')
  413.       {
  414.         Next='*';
  415.         do
  416.         {
  417.           c=fgetc(File);
  418.           if (c=='\n' || c=='\r' || c=='\v' || c=='\f')
  419.             ++*LineCnt;
  420.           if (c=='/' && Next=='/')
  421.             return  ' ';
  422.           if (c=='*')
  423.             Next='/';
  424.           else
  425.             Next='*';
  426.         }
  427.         while (!feof(File));
  428.         return  EOF;
  429.       }
  430.       ungetc(c,File);
  431.       return  '/';
  432.     default:
  433.       return  c;
  434.   }
  435.   return  c;
  436. }
  437.  
  438.  
  439.  
  440. /*  List of C keywords that may be mistaken as function names.
  441.  */
  442.  
  443. char  *KeyWords[] =
  444. {
  445.   "typedef",  "switch",  "sizeof",  "case",  "do",
  446.   "while",    "return",  "else",    "for",   "if"
  447. };
  448.  
  449. /*  void  DoFunc(int, FILE *, int *, int)
  450.  *
  451.  *  Create a function definition/declaration, or add its invokation to
  452.  *  another function's definition.
  453.  */
  454.  
  455. void  DoFunc(int FileName, FILE *File, int *LineCnt, int Level)
  456. {
  457.   int  c,  Prev,  Cnt;
  458.   int  LineNumber = *LineCnt,  Paren = 0;
  459.   char  *Ptr,  *Name;
  460.   char  *DefBase = NULL;
  461.   int    DefLen = 0;
  462.   strFunc  *sPtr;
  463.   unsigned  Temp;
  464.  
  465.   /*  Seperate function name from rest of identifiers contained in ID.
  466.    *  Check that function is not a keyword and return if so.
  467.    */
  468.   Name=IDLen+IDName;
  469.   *Name='\0';
  470.   if (Name[-1]==' ')
  471.     *--Name='\0';
  472.   while (Name-->IDName && *Name!=' ' && *Name!='*')  ;
  473.   if (*++Name=='\0')
  474.     return;
  475.   Cnt=sizeof(KeyWords)/sizeof(char *);
  476.   while (Cnt-->0)
  477.     if (! strcmp(Name,KeyWords[Cnt]) )
  478.       return;
  479.   /*  Call ScanFunc to receive a pointer to the function's Func block.
  480.    *  If nesting level is zero, function is either defined or declared;
  481.    *  If level is more than zero, function is invoked.
  482.    */
  483.   if (( sPtr=ScanFunc(Name) )==NULL)
  484.     return;
  485.   if (Level==0)
  486.   {
  487.     /*  Read formal parameters and make them part of definition, along with
  488.      *  return value contained in ID.
  489.      */
  490.     if (( Ptr=malloc(Name-IDName) )!=NULL)
  491.       memmove(DefBase=Ptr, IDName, DefLen=Name-IDName);
  492.     Prev= c='(';
  493.     IDLen=0;
  494.     do
  495.     {
  496.       if (c=='(')
  497.         ++Paren;
  498.       if (c==')')
  499.         --Paren;
  500.       if (!(c==' ' && Prev==' '))
  501.         IDName[IDLen++]=c;
  502.       Prev=c;
  503.       c=GetChar(File,LineCnt);
  504.     }
  505.     while (Paren && !feof(File));
  506.     if (c==' ')
  507.       while (( c=GetChar(File,LineCnt) )==' ')  ;
  508.     IDName[IDLen++]='\0';
  509.     if (( Ptr=realloc(DefBase,DefLen+IDLen) )!=NULL)
  510.       memmove(DefLen+( DefBase=Ptr ),IDName,IDLen);
  511.     if (c==';' || c==',')
  512.     {
  513.       /*  Definition ends with ; or ,: it is a mere declaration.
  514.        */
  515.       if (sPtr->Definition==NULL)
  516.         sPtr->Definition=DefBase;
  517.       else
  518.         free(DefBase);
  519.       while (c!=';' && !feof(File))
  520.         c=GetChar(File,LineCnt);
  521.     }
  522.     else
  523.     {
  524.       /*  Set information relevant to definition. Then skip everything up to
  525.        *  first { (or: skip the parameters of an unprototyped function.)
  526.        */
  527.       CurFunc=sPtr;
  528.       sPtr->State|=DEFINED;
  529.       sPtr->LineNumber=LineNumber;
  530.       sPtr->FileName=FileName;
  531.       if (sPtr->Definition!=NULL)
  532.         free(sPtr->Definition);
  533.       sPtr->Definition=DefBase;
  534.       if (c!='{')
  535.         while (c!='{' && !feof(File))
  536.           c=GetChar(File,LineCnt);
  537.       ungetc(c,File);
  538.     }
  539.     return;
  540.   }
  541.   /*  Function invokation: add offset-to-List-entry to Calls list of
  542.    *  currently defined function (the definition this invokation is part of);
  543.    *  no offset is added more than once.
  544.    *  Note: The parameters list is not consumed, for it may contain
  545.    *  additional function invokations.
  546.    */
  547.   if (CurFunc==NULL)
  548.     return;
  549.   Temp=sPtr->Calls;
  550.   for (Cnt=0; Cnt<CurFunc->CallCnt; ++Cnt)
  551.     if ( ( (unsigned *) (CurFunc+1) )[Cnt]==Temp)
  552.       return;
  553.   sPtr=realloc(CurFunc, sizeof(strFunc)+(Cnt+1)*sizeof(unsigned) );
  554.   if (sPtr)
  555.   {
  556.     (ListBase+sPtr->Calls)->Struct= CurFunc=sPtr;
  557.     ( (unsigned *) (sPtr+1) )[CurFunc->CallCnt++]=Temp;
  558.   }
  559.   return;
  560. }
  561.  
  562.  
  563. /*  void  *ScanFunc (char *)
  564.  *
  565.  *  Return pointer to function's Func block: create one if inexistent.
  566.  */
  567.  
  568. void  *ScanFunc(char *Name)
  569. {
  570.   int  Length = strlen(Name)+1;
  571.   char  *Ptr,  *Ptr2,  *EndPtr;
  572.   strList  *lPtr;
  573.   strFunc  *sPtr;
  574.  
  575.   /*  Search Name for function's name: if exists, use matching List entry.
  576.    */
  577.   Ptr=NameBase;
  578.   EndPtr=Ptr+NameLen;
  579.   for (lPtr=ListBase; Ptr<EndPtr; ++lPtr)
  580.   {
  581.     if (*Name==*Ptr++)
  582.     {
  583.       Ptr2=Name+1;
  584.       while (*Ptr==*Ptr2 && *Ptr)
  585.         ++Ptr,  ++Ptr2;
  586.       if (*Ptr=='\0' && *Ptr2=='\0')
  587.         break;
  588.     }
  589.     while (*Ptr++)  ;
  590.   }
  591.   /*  If function is encountered for first time, append its name to Name, and
  592.    *  append an entry to List. Otherwise, return pointer to Func block.
  593.    */
  594.   if (Ptr==EndPtr)
  595.   {
  596.     if (( Ptr=realloc(NameBase,NameLen+Length) )==NULL)
  597.       return  NULL;
  598.     strcpy(Ptr+NameLen,Name);
  599.     NameBase=Ptr;
  600.     NameLen+=Length;
  601.  
  602.     if (( lPtr=realloc(ListBase, (ListLen+1)*sizeof(strList) ) )==NULL)
  603.       return  NULL;
  604.     ListBase=lPtr;
  605.     lPtr+=ListLen;
  606.     ++ListLen;
  607.     lPtr->Name=NameLen-Length;
  608.     lPtr->Struct=NULL;
  609.   }
  610.   else
  611.   if (lPtr->Struct)
  612.     return  lPtr->Struct;
  613.   /*  Allocate Func block for function, setting its status to undefined.
  614.    */
  615.   if (( sPtr=malloc(sizeof(strFunc)) )==NULL)
  616.     return  NULL;
  617.   sPtr->State=0;
  618.   sPtr->Definition=NULL;
  619.   sPtr->CallCnt=0;
  620.   sPtr->Calls=lPtr-ListBase;
  621.   lPtr->Struct=sPtr;
  622.   return  sPtr;
  623. }
  624.  
  625.  
  626.  
  627. /*  void  DumpFlow(char *)
  628.  *
  629.  *  Print definition of main() (or any other particular function), followed
  630.  *  by definitions of all other defined functions. If the -u option appeared
  631.  *  twice, also list all undefined functions.
  632.  */
  633.  
  634. void  DumpFlow(char *MainFunc)
  635. {
  636.   strList  *lPtr,  *lEndPtr;
  637.  
  638.   putc('\n',OutFile);
  639.   lPtr=ListBase;
  640.   lEndPtr=lPtr+ListLen;
  641.   for (; lPtr<lEndPtr; ++lPtr)
  642.     if (! strcmp(MainFunc,NameBase+lPtr->Name) )
  643.     {
  644.         DumpFunc(lPtr,0,FALSE);
  645.         break;
  646.     }
  647.  
  648.   for (lPtr=ListBase; lPtr<lEndPtr; ++lPtr)
  649.     if (!( lPtr->Struct->State&PRINTED ))
  650.       DumpFunc(lPtr,0,FALSE);
  651.   putc('\n',OutFile);
  652.  
  653.   if (UnDefined<2)
  654.     return;
  655.   fprintf(OutFile,"\n**  Undefined functions appearing in source:  **\n");
  656.   for (lPtr=ListBase; lPtr<lEndPtr; ++lPtr)
  657.     if (!( lPtr->Struct->State&PRINTED ))
  658.       DumpFunc(lPtr,0,TRUE);
  659.   putc('\n',OutFile);
  660. }
  661.  
  662.  
  663. /*  void  DumpFunc(void *, int, int)
  664.  *
  665.  *  Print function's name and definition. If function is defined, also print
  666.  *  line number, filename, and list all the functions it calls: call DumpFunc
  667.  *  itself for each one.
  668.  */
  669.  
  670. void  DumpFunc(void *Ptr, int  Indent, int PrintUnDefined)
  671. {
  672.   int  Cnt,  i;
  673.   strList  *lPtr = Ptr;
  674.   strFunc  *sPtr = lPtr->Struct;
  675.  
  676.   if (sPtr->State&DEFINED || PrintUnDefined)
  677.   {
  678.     for (i=0; i<Indent; ++i)
  679.       putc(' ',OutFile);
  680.     fprintf(OutFile,"%s",NameBase+lPtr->Name);
  681.     if (sPtr->Definition)
  682.       fprintf(OutFile,": %s",sPtr->Definition);
  683.     if (sPtr->State&DEFINED)
  684.       fprintf(OutFile,",  <%s %u>\n",FileBase+sPtr->FileName,
  685.                                      sPtr->LineNumber+1);
  686.     else
  687.       putc('\n',OutFile);
  688.     /*  If function has been printed before, do not list the functions it
  689.      *  calls once more. Prior to listing, set function's state to printed,
  690.      *  so recursion in source code will not result in an infinite loop.
  691.      */
  692.     if (sPtr->State==DEFINED)
  693.     {
  694.       sPtr->State|=PRINTED;
  695.       for (Cnt=0; Cnt<sPtr->CallCnt; ++Cnt)
  696.       {
  697.         lPtr=ListBase+( (unsigned *) (sPtr+1) )[Cnt];
  698.         if (lPtr->Struct->State&DEFINED || UnDefined)
  699.           DumpFunc(lPtr,Indent+IND_ADD,TRUE);
  700.       }
  701.     }
  702.   }
  703. }
  704.  
  705.  
  706.