You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1288 lines
39 KiB
1288 lines
39 KiB
/***************************************************************************
|
|
*striphdr.c - Program to read source files and produce alphabetised
|
|
* listing of header blocks, telling which files they came
|
|
* from.
|
|
*
|
|
* Copyright (c) 1983-2001, Microsoft Corporation. All rights reserved.
|
|
*
|
|
*Author: Tom Corbett, Brian Lewis - Microsoft Corp.
|
|
*
|
|
*Usage: striphdr [switches] file {file ...}
|
|
* Accepts native code or C source; determines source
|
|
* type based on filename suffix.
|
|
* Wildcards may be used in the file names.
|
|
*
|
|
* switches:
|
|
*
|
|
* -m process module headers only
|
|
* -b process both procedure and module headers
|
|
* [default is procedure headers only]
|
|
* -l process only first line of each header
|
|
* -n process none of header (i.e. lists only function name)
|
|
* -s <name> process named section only (may be used with -l)
|
|
* [default is to process whole header]
|
|
* -d delete processed section from input file
|
|
* -q quiet, do not print headers (useful only with -d)
|
|
* -x <ext> gives extension for output file when -d used
|
|
* [default: .new]
|
|
* -r remove revision histories from file headers
|
|
* (equivalent to -m -d -q -s "Revision History")
|
|
*
|
|
*Purpose:
|
|
* Given a list of Native code and/or C sources, strip out routine or module
|
|
* headers and produce an alphabetised listing of the headers, telling where
|
|
* (filename) each header came from. Optionally, delete part or all of the
|
|
* headers from the source file.
|
|
*
|
|
* For each file on the command line (wildcards may be used), striphdr reads
|
|
* the file, and records information from the file and/or procedure headers.
|
|
* This information is then sorted by procedure name/file name, and printed
|
|
* to stdout, with an indication of which file each procedure is from. If
|
|
* the -d flag is activated, the information is also deleted from the input
|
|
* file and the new file placed in a file with the same name but the
|
|
* extension .new (this extension can be changed with the -x switch). The
|
|
* actual input file is also left as is. When using the -d switch, -q will
|
|
* eliminate the output, so that only the deleting action takes place. By
|
|
* default only the procedure headers are scanned, the -m and -b switches
|
|
* change this. By default all the information in a header is printed or
|
|
* deleted, the -l, -n, and -s switches change this. The -r switch is an
|
|
* abbreviation which will remove revision histories from the file headers.
|
|
*
|
|
* Input filenames with suffixes of .c or .h are assumed to contain C source,
|
|
* and suffixes of .asm or .inc imply native code source. Routine or module
|
|
* names must be on the first or second line following the start of header;
|
|
* if on the second line then the first line must contain only whitespace
|
|
* after an optional '*' (if C source) or ';' (if native code source).
|
|
* Routine names can contain a return type and parameters; multiple entry
|
|
* point should be seperated by commas. Header start and end symbols must
|
|
* start in the left-most column. Module headers and routine headers are
|
|
* marked in the same manner; position relative to the beginning of file is
|
|
* used to determine which header type is appropriate.
|
|
*
|
|
* Source is detabbed (1 tab assumed to equal 4 spaces in C, 8 spaces in native
|
|
* code) and routine names are parsed to ensure that the correct name is
|
|
* grabbed for sorting.
|
|
*
|
|
* C Headers are started with a '/' characters in the first column followed
|
|
* by at least 3 '*' characters, and are ended with at least 4 '*' characters
|
|
* followed by a '/'. Each line within a header must begin with a '*', except
|
|
* lines beginning with '#if', '#else', or '#endif'. Module headers must be
|
|
* preceded with nothing except (perhaps) blank lines; if any non-blank lines
|
|
* are found prior to the first header in a module, it is assumed that the
|
|
* header belongs to a routine, otherwise, to the entire module.
|
|
*
|
|
* Native code headers are started by a ';' followed by at least 3 '*'
|
|
* characters, and the header end is denoted by a ';' followed by at least
|
|
* 4 '*' characters. There must be a ';' character in the left-most column of
|
|
* every line of the header block; the only exception to this is that the '
|
|
* if', 'else', and 'endif' switches are allowed inside header blocks. Module
|
|
* headers can be preceded with any number of blank lines, a TITLE statement,
|
|
* a NAME statement, and/or a PAGE statement; if anything else is
|
|
* encountered, subsequent headers will be assumed to be routine headers.
|
|
*
|
|
* Sections within a header must have a title beginning on the 2nd
|
|
* character of the line, and the section is assumed to extend to the next
|
|
* line with a non-blank character in position 2.
|
|
*
|
|
* No non-header comments should begin in column 1 with '/***' in C or
|
|
* ';***' in native code; this will confuse striphdr.
|
|
*
|
|
*
|
|
*Revision History:
|
|
*
|
|
* 01-01-83 TOMC Created [tomc]
|
|
* 09-09-85 BL Modified to allow the option of stripping module headers
|
|
* instead of routine headers via the -m switch
|
|
* 09-30-85 BL Modified to accept either C Source or native code source.
|
|
* Modified to detab C Source assuming 3 spaces per tab.
|
|
* Modified to parse C Routine names properly, allowing
|
|
* types, macros, etc. to precede routine names.
|
|
* Modified to make the sorting be case insensitive.
|
|
* 11-12-85 BL Fixed bug that caused 1st char to always be removed from
|
|
* headers, and that caused problems when a blank line follows
|
|
* header starts.
|
|
* 06-01-87 PHG fixed GetNonBlank()
|
|
* allowed 'title' and 'Title" as well as 'TITLE'
|
|
* allowed NAME, PAGE as well as TITLE
|
|
* made tab expansion for both ASM and C files
|
|
* both C and ASM procedure names can have args,
|
|
* multiple entries
|
|
* simplified some code and a few minor bug fixes
|
|
* added -b, -n, -s, -d, -q, -x, -r switches
|
|
* 05-10-90 JCR Accept .cxx/.hxx files (C++ support), misc cleanup
|
|
* 04-06-95 SKS Accept .cpp/.hpp as equivalent to .cxx/.hxx, respectively
|
|
***************************************************************************/
|
|
|
|
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <io.h>
|
|
#include <direct.h>
|
|
|
|
|
|
#define TRUE 1
|
|
#define FALSE 0
|
|
#define NAMBUFSZ 20000 /* size of buffer for holding all procedure names */
|
|
#define LINEBUFSZ 250 /* maximum size of 1 physical line */
|
|
#define MAXENT 3000 /* maximum number of procedure entry points */
|
|
#define MAXFUNCS 2000 /* maximum number of function headers */
|
|
|
|
/* abort codes */
|
|
#define NMBUFOV 0 /* name buffer table overflow */
|
|
#define FILENF 1 /* file not found */
|
|
#define FUNCSOV 2 /* function table overflow */
|
|
#define ENTOV 3 /* entry table overflow */
|
|
#define FILENO 4 /* cannot open file */
|
|
#define OUTSPACE 5 /* out of disk space */
|
|
|
|
/* tab sizes, pagelength */
|
|
#define TABSIZE_C 4
|
|
#define TABSIZE_ASM 8 /* tab sizes for code */
|
|
#define PAGELEN 60 /* pagelength for page breaks; 60 for HP LaserJet */
|
|
|
|
struct {
|
|
char *entname; /* name of entry point */
|
|
int funcid; /* function index for this entry point */
|
|
char printed; /* flag to ensure each entry printed just once */
|
|
}
|
|
entry [MAXENT];
|
|
int nentries; /* number of entries parsed so far */
|
|
|
|
struct {
|
|
char *filename; /* name of file which contains this function */
|
|
long filepos; /* seek position on file "temp" */
|
|
int nlines; /* number of lines in function header */
|
|
}
|
|
func [MAXFUNCS];
|
|
int nfuncs; /* number of function parsed so far */
|
|
|
|
long ofpos; /* output file seek position */
|
|
char linebuf[LINEBUFSZ];
|
|
char nambuf[NAMBUFSZ]; /* buffer for holding function names */
|
|
int nambufi; /* next free byte in nambuf */
|
|
char fEof;
|
|
FILE *ofd; /* output file descriptor */
|
|
FILE *ifd; /* input file descriptor */
|
|
FILE *cfd; /* copy file descriptor */
|
|
FILE *fopen();
|
|
int outline; /* output line number (1..PAGELEN) for page ejects */
|
|
int fDelete = FALSE; /* TRUE if copy and delete instead of display */
|
|
int fGetModNams = FALSE; /* TRUE if strip module names */
|
|
int fGetProcNams = TRUE; /* TRUE if strip procedure names */
|
|
int fC_Source; /* TRUE if we're processing C source, false if .asm */
|
|
int fDoAll = TRUE; /* TRUE if we process all of header */
|
|
int fDoFirst = FALSE; /* TRUE if we process first line of header */
|
|
int fDoSection = FALSE; /* TRUE if we process named section of header */
|
|
int fQuiet = FALSE; /* TRUE is quiet mode */
|
|
char SectionHead[80]; /* Section name */
|
|
char CopyExt[4] = "new"; /* extension of new files */
|
|
char *szFilenameCur; /* name of file being read (for error reporting) */
|
|
|
|
|
|
|
|
/***
|
|
*MyAbort(code, s) - abort program with message
|
|
*Purpose:
|
|
* abort the program with correct message.
|
|
*
|
|
*Entry:
|
|
* code = error code
|
|
* s = filename for code == FILENF
|
|
*
|
|
*Exit:
|
|
* exits to DOS
|
|
*
|
|
*Exceptions:
|
|
*
|
|
*******************************************************************************/
|
|
|
|
void MyAbort(code, s)
|
|
int code;
|
|
char *s;
|
|
{
|
|
fprintf(stderr, "Fatal Error - ");
|
|
if (code == NMBUFOV)
|
|
fprintf(stderr, "NAME Buffer overflow\n");
|
|
if (code == FILENF)
|
|
fprintf(stderr, "File not found: %s\n", s);
|
|
if (code == FUNCSOV)
|
|
fprintf(stderr, "Function table overflow\n");
|
|
if (code == ENTOV)
|
|
fprintf(stderr, "Entry table overflow\n");
|
|
if (code == FILENO)
|
|
fprintf(stderr, "Cannot open file: %s\n", s);
|
|
if (code == OUTSPACE)
|
|
fprintf(stderr, "Out of disk space\n", s);
|
|
|
|
exit(code);
|
|
} /* MyAbort */
|
|
|
|
|
|
/***
|
|
*FindIfC(szFile) - find if filename if C or ASM
|
|
*
|
|
*Purpose:
|
|
* Set global flag fC_Source to TRUE if the filename ends in
|
|
* .c, .h, .cxx (.cpp), or .hxx (.hpp); FALSE if it ends in .asm or .inc.
|
|
* The extension comparison is NOT case senstive. If none of
|
|
* these are found, don't set the flag,and return FALSE,
|
|
* else return TRUE.
|
|
*
|
|
*Exit:
|
|
* sets flag fC_Source
|
|
* returns FALSE if neither C nor ASM source
|
|
* returns TRUE if fC_Source set
|
|
********************************************************************/
|
|
|
|
FindIfC(szFile)
|
|
char *szFile;
|
|
{
|
|
int cbFileName = strlen(szFile);
|
|
char *pbPastName = szFile + cbFileName; /* points at NULL at string end */
|
|
|
|
if ((!_stricmp (".c", pbPastName - 2)) ||
|
|
(!_stricmp (".h", pbPastName - 2)) ||
|
|
(!_stricmp (".s", pbPastName - 2)) ||
|
|
(!_stricmp (".cpp", pbPastName - 4)) ||
|
|
(!_stricmp (".hpp", pbPastName - 4)) ||
|
|
(!_stricmp (".cxx", pbPastName - 4)) ||
|
|
(!_stricmp (".hxx", pbPastName - 4)))
|
|
fC_Source = TRUE; /* file is assumed to be C source */
|
|
|
|
else if ((!_stricmp (".asm", pbPastName - 4)) ||
|
|
(!_stricmp (".inc", pbPastName - 4)))
|
|
fC_Source = FALSE; /* file is assumed to be Native Code source */
|
|
|
|
else return (FALSE); /* didn't find an appropriate suffix */
|
|
|
|
return (TRUE);
|
|
|
|
} /* FindIfC */
|
|
|
|
|
|
/***
|
|
*MakeCopyfileName(filename, copyname) - make copyfile name from input name
|
|
*
|
|
*Purpose:
|
|
* Put the copy file suffix onto a file name
|
|
*
|
|
*Entry:
|
|
* filename = input file name
|
|
*
|
|
*Exit:
|
|
* copyname = filled in with copy file name
|
|
*
|
|
*Exceptions:
|
|
*
|
|
*******************************************************************************/
|
|
|
|
void MakeCopyfileName(filename, copyname)
|
|
char *filename, *copyname;
|
|
{
|
|
char *p;
|
|
|
|
strcpy(copyname, filename);
|
|
p = copyname + strlen(copyname);
|
|
while (*(--p) != '.')
|
|
; /* p points to '.' */
|
|
*(p+1) = '\0'; /* cut off string after '.' */
|
|
strcat(copyname, CopyExt); /* put on copy file extension */
|
|
}
|
|
|
|
|
|
|
|
/***
|
|
*linelen(pLine)
|
|
*
|
|
*Purpose:
|
|
* Return the number of characters in a passed line. The line terminator
|
|
* ('\n') is not included in the count.
|
|
*
|
|
*Entry:
|
|
* pLine = a pointer to the first char in the line.
|
|
*Exit:
|
|
* returns the number of characters in the line (an int).
|
|
***************************************************************************/
|
|
|
|
int linelen(pLine)
|
|
char *pLine;
|
|
{
|
|
register int index;
|
|
|
|
for (index = 0; *(pLine + index) != '\n'; index++);
|
|
return (index);
|
|
}
|
|
|
|
|
|
|
|
static int cLinesRead;
|
|
static char fInHeader = FALSE;
|
|
|
|
/***
|
|
*ReadLine(copyit) - read line and analyze it
|
|
*
|
|
*Purpose:
|
|
* read 1 line from input file into 'linebuf'.
|
|
* if copyit == TRUE, copy previous line to copy file
|
|
*
|
|
*Entry:
|
|
* copyit = TRUE means copy the line to the copy file
|
|
*
|
|
*Exit:
|
|
* returns -1 for EOF,
|
|
* 0 for vanilla lines,
|
|
* if C Source,
|
|
* 1 for lines which begin "/***" (start of header)
|
|
* 2 for lines which begin "/****" (end of header)
|
|
* if Native Code Source,
|
|
* 1 for lines which begin ";***" (start of header)
|
|
* 2 for lines which begin ";****" (end of header)
|
|
*
|
|
***************************************************************************/
|
|
|
|
int ReadLine(copyit)
|
|
int copyit; /* TRUE = copy line to copy file */
|
|
{
|
|
register int i;
|
|
register int cbLine;
|
|
|
|
if (copyit) {
|
|
if (fputs(linebuf, cfd) == EOF)
|
|
MyAbort(OUTSPACE);
|
|
} /* copy previous line if requested */
|
|
|
|
if ((fgets(linebuf, LINEBUFSZ, ifd)) == NULL) {
|
|
fEof = TRUE;
|
|
return(-1);
|
|
}
|
|
|
|
cLinesRead++;
|
|
cbLine = linelen(linebuf);
|
|
|
|
if ((linebuf[0] == ';') && (!fC_Source)) { /* native code comment */
|
|
if ((linebuf[1] == '*') && (linebuf[2] == '*') && (linebuf[3] == '*'))
|
|
/* know we have either start or end of a header */
|
|
if (fInHeader) {
|
|
fInHeader = FALSE;
|
|
if (linebuf[4] == '*')
|
|
return (2); /* valid end of header */
|
|
/* error, got start of header when we're already in a header */
|
|
fprintf(stderr, "Unterminated header for function: %s\n",
|
|
entry[nentries].entname);
|
|
fprintf(stderr, " before line %d of file %s\n",
|
|
cLinesRead, szFilenameCur);
|
|
}
|
|
else { /* have a valid start of header */
|
|
fInHeader = TRUE;
|
|
return (1);
|
|
}
|
|
}
|
|
|
|
else if ((linebuf[0] == '/') && (fC_Source)) { /* C code comment? */
|
|
if ((linebuf[1] == '*') && (linebuf[2] == '*') && (linebuf[3] == '*'))
|
|
if (fInHeader) {
|
|
/* error, got start of header when we're already in a header */
|
|
fprintf(stderr, "Unterminated header for function: %s\n",
|
|
entry[nentries].entname);
|
|
fprintf(stderr, " before line %d of file %s\n",
|
|
cLinesRead, szFilenameCur);
|
|
}
|
|
else {
|
|
fInHeader = TRUE;
|
|
return (1); /* valid start of header */
|
|
}
|
|
}
|
|
|
|
else if ((linebuf[0] == '*') && (fC_Source)) { /* C code comment? */
|
|
for (i = 1; (i < cbLine) && (linebuf[i] == '*'); i++);
|
|
|
|
if ((linebuf[i] == '/') && (i > 3)) { /* have a valid end of header */
|
|
if (fInHeader) {
|
|
fInHeader = FALSE;
|
|
return (2);
|
|
}
|
|
/* got a header terminator when we weren't within a header */
|
|
fprintf(stderr, "Illegal termination to header for function: %s",
|
|
entry[nentries].entname);
|
|
fprintf(stderr, " in line %d of file %s\n",
|
|
cLinesRead, szFilenameCur);
|
|
} /* if */
|
|
} /* else if */
|
|
|
|
return(0); /* vanilla line */
|
|
} /* ReadLine */
|
|
|
|
|
|
/***
|
|
*WriteLine(pb, of) - write a line and expand tabs
|
|
*
|
|
*Purpose:
|
|
* Write a zero terminated string to file 'of'.
|
|
* If we're writing C Source header lines, detab them as we go.
|
|
*
|
|
*Entry:
|
|
* pb = points to 1st byte of 0-terminated string.
|
|
* of = aft index for destination file.
|
|
*
|
|
*Exit:
|
|
* 'ofpos' is bumped to reflect current output file position.
|
|
*
|
|
***************************************************************************/
|
|
|
|
void WriteLine(pb, of)
|
|
register char *pb;
|
|
FILE *of;
|
|
{
|
|
register int linepos = 0; /* column position on output line */
|
|
int modTS; /* linepos mod tabSize */
|
|
int cSpaces; /* number of spaces to print to next tab stop */
|
|
int index; /* 'for' index for printing spaces */
|
|
int tabSize; /* current tab size */
|
|
|
|
if (fC_Source)
|
|
tabSize = TABSIZE_C;
|
|
else
|
|
tabSize = TABSIZE_ASM;
|
|
|
|
while (*pb != '\0') {
|
|
if (*pb == '\t') { /* if a tab character found */
|
|
modTS = linepos % tabSize;
|
|
cSpaces = tabSize - modTS;
|
|
for (index = 0; index < cSpaces; index++) {
|
|
fputc(' ', of);
|
|
linepos++;
|
|
}
|
|
} /* if */
|
|
else {
|
|
fputc(*pb, of);
|
|
linepos++;
|
|
}
|
|
pb++;
|
|
} /* while */
|
|
|
|
if (ferror(of))
|
|
MyAbort(OUTSPACE);
|
|
ofpos = ftell(of); /* update output file seek position for next line */
|
|
} /* WriteLine */
|
|
|
|
|
|
/***
|
|
*SwitchFound(s) - see if IF, ELSE, ENDIF switch on this line
|
|
*
|
|
*Purpose: return TRUE if an "if", "else", or "endif" are identified at
|
|
* the start of string 's'. This is used to check to see if a
|
|
* header line which doesn't start with ';' is an assembler switch.
|
|
*Entry:
|
|
* s = pointer to a char which represents the string to be checked.
|
|
*
|
|
*Exit:
|
|
* returns TRUE if the string matches "if", "else", or "endif" of either
|
|
* upper or lower case, FALSE otherwise.
|
|
***************************************************************************/
|
|
|
|
SwitchFound(s)
|
|
char *s;
|
|
{
|
|
if (fC_Source) {
|
|
/* strncmp returns 0 (which maps to FALSE) iff a match was found */
|
|
if (strncmp(s, "#if", 3) &&
|
|
strncmp(s, "#IF", 3) &&
|
|
strncmp(s, "#else", 5) &&
|
|
strncmp(s, "#ELSE", 5) &&
|
|
strncmp(s, "#endif", 6) &&
|
|
strncmp(s, "#ENDIF", 6))
|
|
return (FALSE);
|
|
}
|
|
else {
|
|
if (strncmp(s, "if", 2) &&
|
|
strncmp(s, "IF", 2) &&
|
|
strncmp(s, "else", 4) &&
|
|
strncmp(s, "ELSE", 4) &&
|
|
strncmp(s, "endif", 5) &&
|
|
strncmp(s, "ENDIF", 5))
|
|
return (FALSE);
|
|
}
|
|
return (TRUE);
|
|
}
|
|
|
|
/***
|
|
*GetNonBlank(copyit) - read line, find first non blank character
|
|
*
|
|
*Purpose:
|
|
* Read in a line; return linebuf index if non-blank line found, -1 otherwise
|
|
*
|
|
*Entry:
|
|
* copyit = TRUE means copy line to copy file
|
|
*
|
|
*Exit:
|
|
* returns -1 if blank line read
|
|
* return index of first non-whitespace char otherwise
|
|
*
|
|
***************************************************************************/
|
|
GetNonBlank(copyit)
|
|
int copyit; /* copy lines to copy file if TRUE */
|
|
{
|
|
register int i;
|
|
register int cbLine;
|
|
|
|
if (ReadLine(copyit) == -1) {
|
|
return(-1);
|
|
}
|
|
|
|
cbLine = linelen(linebuf);
|
|
|
|
for (i = 0; ((linebuf[i] == ' ') || (linebuf[i] == '\t')) &&
|
|
(i < cbLine); i++);
|
|
|
|
if (i >= cbLine)
|
|
return (-1);
|
|
else
|
|
return (i);
|
|
|
|
} /* GetNonBlank */
|
|
|
|
/***
|
|
*ReadToHeader() - reads to beginning of next header
|
|
*
|
|
*Purpose:
|
|
* Reads to beginning of next header or EOF
|
|
*
|
|
*Entry:
|
|
*
|
|
*Exit:
|
|
* Returns -1 if at EOF, 0 if at a header
|
|
*
|
|
************************************************************************/
|
|
|
|
int ReadToHeader()
|
|
{
|
|
while (!fEof && ReadLine(fDelete) != 1)
|
|
;
|
|
if (fEof)
|
|
return -1; /* at EOF */
|
|
else
|
|
return 0; /* at beginning of header */
|
|
}
|
|
|
|
|
|
|
|
/***
|
|
*Get1stHdr() - read to first header
|
|
*
|
|
*Purpose:
|
|
* Read lines until the first header is found. Return TRUE if this is
|
|
* the header for the module, or FALSE if no module header is found.
|
|
*
|
|
* In C Sources, we assume that if the first non-blank line in the source
|
|
* starts with '/***', then it's the start of a module header.
|
|
* In Native-Code Sources, we assume that there can exist any number
|
|
* of blank lines, optionally followed by a TITLE statement, followed
|
|
* by any number of blanks lines, and then by the module header if
|
|
* it exists.
|
|
*
|
|
*Entry:
|
|
* NONE
|
|
*Exit:
|
|
* TRUE if module header found, FALSE otherwise. Note that if fEof is found,
|
|
* FALSE is returned, and if !fEof and no module header is found, this
|
|
* routine will read in lines until the first routine header is found,or
|
|
* fEof.
|
|
***************************************************************************/
|
|
|
|
Get1stHdr()
|
|
{
|
|
register int index;
|
|
register int i;
|
|
|
|
while (((index = GetNonBlank(fDelete)) == -1) && (!fEof))
|
|
;
|
|
|
|
if (fEof) {
|
|
fprintf(stderr, "warning: no file header on file ");
|
|
fprintf(stderr, "%s\n", szFilenameCur);
|
|
return (FALSE);
|
|
}
|
|
|
|
/* now, index is set into linebuf for a non-blank character */
|
|
if (fC_Source) { /* if header exists, must be first non-blank line */
|
|
if ((linebuf[0] == '/') && (linebuf[1] == '*') &&
|
|
(linebuf[2] == '*') && (linebuf[3] == '*')) {
|
|
fInHeader = TRUE;
|
|
return (TRUE);
|
|
}
|
|
else {
|
|
/* read to start of first header, tell caller no module header found */
|
|
ReadToHeader();
|
|
fprintf(stderr, "warning: no file header on file ");
|
|
fprintf(stderr, "%s\n", szFilenameCur);
|
|
return (FALSE);
|
|
}
|
|
}
|
|
|
|
/* must be native-code source - - - can have a TITLE line with blank
|
|
lines before and after it - - - all prior to module header */
|
|
|
|
for (i = 1; i <= 3; ++i) { /* do thrice, might have TITLE, NAME, and PAGE */
|
|
if (strncmp("TITLE", linebuf + index, 5) == 0 ||
|
|
strncmp("title", linebuf + index, 5) == 0 ||
|
|
strncmp("Title", linebuf + index, 5) == 0 ||
|
|
strncmp("NAME", linebuf + index, 4) == 0 ||
|
|
strncmp("name", linebuf + index, 4) == 0 ||
|
|
strncmp("Name", linebuf + index, 4) == 0 ||
|
|
strncmp("PAGE", linebuf + index, 4) == 0 ||
|
|
strncmp("Page", linebuf + index, 4) == 0 ||
|
|
strncmp("page", linebuf + index, 4) == 0)
|
|
{ /* found TITLE, NAME, or PAGE statement */
|
|
while (((index = GetNonBlank(fDelete)) == -1) && (!fEof))
|
|
;
|
|
|
|
if (fEof) {
|
|
fprintf(stderr, "warning: no file header on file ");
|
|
fprintf(stderr, "%s\n", szFilenameCur);
|
|
return (FALSE);
|
|
}
|
|
}
|
|
}
|
|
|
|
/* if there was a TITLE and/or NAME statement, we've eaten it,
|
|
and any following blank lines;
|
|
must now have module header, if it exists */
|
|
|
|
if ((linebuf[0] == ';') && (linebuf[1] == '*') &&
|
|
(linebuf[2] == '*') && (linebuf[3] == '*')) {
|
|
fInHeader = TRUE;
|
|
return (TRUE);
|
|
}
|
|
else {
|
|
/* read to start of first header, tell caller no module header found */
|
|
ReadToHeader();
|
|
fprintf(stderr, "warning: no file header on file ");
|
|
fprintf(stderr, "%s\n", szFilenameCur);
|
|
return (FALSE);
|
|
}
|
|
|
|
} /* Get1stHdr */
|
|
|
|
/***
|
|
*AdvanceOne(s) - advance one char if one whitespace or comment token
|
|
*
|
|
*Purpose:
|
|
* 's' points to the first character in an input line; if it's a whitespace
|
|
* token or a comment token, return a pointer to the next character in the
|
|
* line, otherwise, return 's' unchanged.
|
|
*
|
|
*Entry:
|
|
* s = ptr to first char in input line
|
|
*
|
|
*Exit:
|
|
* returns ptr the next char if *s is whatspace, ';', or '*',
|
|
* otherwise returns s
|
|
*
|
|
*******************************************************************************/
|
|
|
|
char *AdvanceOne(s)
|
|
char *s;
|
|
{
|
|
if (*s == '*') {
|
|
if (fC_Source)
|
|
s++;
|
|
}
|
|
else if (*s == ';') {
|
|
if (!fC_Source)
|
|
s++;
|
|
}
|
|
else if ((*s == ' ') || (*s == '\t'))
|
|
s++;
|
|
return(s);
|
|
} /* AdvanceOne */
|
|
|
|
|
|
/***
|
|
*FuncNamePtr(pbName) - find function name from this or next line
|
|
*
|
|
*Purpose:
|
|
* Given a pointer into the current header line, return a pointer to
|
|
* the first non-blank or comment symbol; if none found in the existing
|
|
* line, read in another and try that one.
|
|
* if a C routine line, skip any types, etc., and return a pointer to
|
|
* the filename itself.
|
|
*
|
|
* If a C routine line:
|
|
* -------------------
|
|
* Assumes that C routine names will end with a left paren.
|
|
* Allows left parens in a summary of purpose if said summary is
|
|
* preceded by a '-' which comes after the routine name.
|
|
* Assumes that there are 1 or 0 spaces between the the routine name
|
|
* and the mandatory left paren.
|
|
*
|
|
*Entry:
|
|
* pbName = a pointer to the place to begin searching in linebuf.
|
|
*Exit:
|
|
* Returns a pointer to the filename.
|
|
*
|
|
***************************************************************************/
|
|
|
|
char *FuncNamePtr(pbName)
|
|
register char *pbName;
|
|
{
|
|
register int cbLine;
|
|
char *pbTmp;
|
|
int iStart;
|
|
|
|
/* first, see if we have a blank line - if so, read in another one,
|
|
and assume that it has the filename; don't check that one ... */
|
|
pbTmp = AdvanceOne(pbName);
|
|
cbLine = linelen(pbTmp);
|
|
for (iStart = 0; ((*(pbTmp+iStart) == ' ') || (*(pbTmp+iStart) == ' ')) &&
|
|
(iStart < cbLine); iStart++);
|
|
if (iStart >= cbLine) {
|
|
if (ReadLine(fDelete) != 0) { /* then we got a procedure end w/out a name! */
|
|
if (fEof)
|
|
fprintf(stderr, "EOF not expected\n");
|
|
else
|
|
fprintf(stderr, "Illegal header termination\n");
|
|
fprintf(stderr,
|
|
" in line %d of file %s\n", cLinesRead, szFilenameCur);
|
|
return((char *)-1);
|
|
}
|
|
pbName = linebuf;
|
|
}
|
|
|
|
/* now, assume that there is a filename somewhere at or after pbName */
|
|
|
|
|
|
iStart = 0;
|
|
|
|
while (*(pbName+iStart) != '(' && *(pbName+iStart) != '-' &&
|
|
*(pbName+iStart) != ',' && *(pbName+iStart) != '\n')
|
|
++iStart; /* search fwd for '(', '-', ',' or '\n' */
|
|
do {
|
|
--iStart;
|
|
}
|
|
while ((*(pbName+iStart) == ' ' || *(pbName+iStart) == '\t') &&
|
|
iStart > 0);
|
|
/* search back through whitespace */
|
|
while (*(pbName+iStart) != ' ' && *(pbName+iStart) != '\t' &&
|
|
*(pbName+iStart) != ';' && *(pbName+iStart) != '*')
|
|
--iStart;
|
|
/* search back to beginning of name */
|
|
++iStart; /* point to first letter of name */
|
|
return (pbName+iStart); /* return pointer to name */
|
|
} /* FuncNamePtr */
|
|
|
|
|
|
/***
|
|
*MyReadFile(pbNam) - read file and remember headers
|
|
*
|
|
*Purpose:
|
|
* Read one entire file, stripping out and remembering function headers
|
|
* or module headers, as appropriate.
|
|
*
|
|
***************************************************************************/
|
|
|
|
void MyReadFile(char *pbNam)
|
|
{
|
|
char CopyName[65]; /* name of copy file */
|
|
char *s, done, backout;
|
|
int funci, svnfuncs, svnentries;
|
|
int inmodhdr; /* TRUE if in module header, else FALSE */
|
|
int insection; /* TRUE if in scetion to process */
|
|
int onfirst; /* TRUE if on first line of header */
|
|
|
|
szFilenameCur = pbNam;
|
|
if ((ifd = fopen(pbNam, "r")) <= 0)
|
|
MyAbort(FILENF, pbNam);
|
|
linebuf[0] = '\0'; /* make linebuf blank so file copying works */
|
|
fEof = FALSE;
|
|
cLinesRead = 0;
|
|
|
|
if (!FindIfC(pbNam)) { /* filename had invalid suffix */
|
|
fprintf(stderr, "Illegal suffix for file %s", pbNam);
|
|
fprintf(stderr, "; must have .c or .asm suffix\n");
|
|
fclose(ifd);
|
|
return;
|
|
}
|
|
|
|
if (fDelete) {
|
|
MakeCopyfileName(pbNam, CopyName); /* put the right extension on */
|
|
cfd = fopen(CopyName, "w");
|
|
if (cfd == NULL)
|
|
MyAbort(FILENO, CopyName); /* can't open file */
|
|
}
|
|
|
|
inmodhdr = Get1stHdr();
|
|
if (inmodhdr && !fGetModNams) {
|
|
/* don't want module header */
|
|
ReadToHeader(); /* skip module header */
|
|
inmodhdr = FALSE;
|
|
}
|
|
|
|
/* we are currently at the beginning of a new header.
|
|
copy it to file TEMP, remember all entry points in NAMBUF. */
|
|
|
|
while (!fEof && (inmodhdr || fGetProcNams)) {
|
|
svnfuncs = nfuncs;
|
|
svnentries = nentries;
|
|
if (++nfuncs >= MAXFUNCS)
|
|
MyAbort(FUNCSOV);
|
|
func[nfuncs].filename = pbNam;
|
|
func[nfuncs].nlines = 0;
|
|
func[nfuncs].filepos = ofpos;
|
|
if (ReadLine(fDelete) == -1)
|
|
done = TRUE; /* got end-of-file */
|
|
else
|
|
done = FALSE;
|
|
funci = nfuncs;
|
|
s = linebuf;
|
|
|
|
while (!done) {
|
|
if (++nentries >= MAXENT)
|
|
MyAbort(ENTOV);
|
|
entry[nentries].entname = nambuf + nambufi;
|
|
entry[nentries].printed = FALSE;
|
|
entry[nentries].funcid = funci;
|
|
s = FuncNamePtr(s);
|
|
if (s == (char *)-1) {
|
|
fprintf(stderr, "Error found in line %d of file %s\n",
|
|
cLinesRead, szFilenameCur);
|
|
s = linebuf;
|
|
}
|
|
while ((*s != '\0') && /* transfer function name to nambuf */
|
|
(*s != ',') &&
|
|
(*s != '(') &&
|
|
(*s != ' ') &&
|
|
(*s != '\n') &&
|
|
(*s != '\t') &&
|
|
(nambufi < (NAMBUFSZ - 2))) {
|
|
nambuf[nambufi++] = *(s++);
|
|
}
|
|
if (nambufi >= (NAMBUFSZ - 2))
|
|
MyAbort(NMBUFOV, NULL);
|
|
nambuf[nambufi++] = '\0';
|
|
|
|
/* make all secondary entry points reference primary entry point */
|
|
if (funci >= 0)
|
|
funci = -1 - nentries;
|
|
|
|
/* next we see if another entry point exists */
|
|
while (*s == ' ' || *s == '\t')
|
|
++s; /* skip whitespace */
|
|
if (*s == '(') {
|
|
do {
|
|
++s;
|
|
}
|
|
while (*s != ')' && *s != '\n'); /* skip param list */
|
|
++s; /* goto next character */
|
|
}
|
|
while (*s == ' ' || *s == '\t')
|
|
++s; /* skip whitespace */
|
|
if (*(s++) != ',')
|
|
done = TRUE; /* no more entry pts */
|
|
else {
|
|
++s;
|
|
done = FALSE;
|
|
}
|
|
} /* while !done */
|
|
|
|
backout = FALSE;
|
|
|
|
if (fDoAll || fDoFirst)
|
|
insection = TRUE;
|
|
else
|
|
insection = FALSE; /* are we in the correct section */
|
|
|
|
onfirst = TRUE; /* on first line */
|
|
|
|
do {
|
|
s = linebuf;
|
|
|
|
if ((!fC_Source && (*s != ';') && !SwitchFound(s)) ||
|
|
(fC_Source && (*s != '*') && !SwitchFound(s))) {
|
|
/* Illegal Header Format, leave garbage in file TEMP,
|
|
but restore nfuncs and nentries to previous values
|
|
and backout of this header gracefully. */
|
|
fprintf(stderr, "Invalid Header for function: %s",
|
|
entry[nentries].entname);
|
|
fprintf(stderr, " in line %d", cLinesRead);
|
|
fprintf(stderr, ", in file: %s\n", pbNam);
|
|
nfuncs = svnfuncs;
|
|
nentries = svnentries;
|
|
backout = TRUE;
|
|
}
|
|
|
|
s = AdvanceOne(s);
|
|
|
|
if (!fDoAll && fDoSection && !onfirst && *s != ' ' && *s != '\t' && *s != '\n') {
|
|
/* now at a section beginning -- and it's significant */
|
|
if (insection)
|
|
insection = FALSE; /* come to end of section */
|
|
else if (strncmp(s, SectionHead, strlen(SectionHead)) == 0)
|
|
insection = TRUE; /* come to beginning of section */
|
|
}
|
|
|
|
/* if in the section, don't copy, but write to TEMP */
|
|
if (insection) {
|
|
WriteLine(s, ofd);
|
|
func[nfuncs].nlines++;
|
|
ReadLine(FALSE);
|
|
}
|
|
else {
|
|
ReadLine(fDelete);
|
|
}
|
|
|
|
if (onfirst && !fDoAll)
|
|
insection = FALSE;
|
|
|
|
onfirst = FALSE; /* no longer on first line */
|
|
}
|
|
while ((!fEof) && (!backout) && fInHeader);
|
|
|
|
/* skip to start of next function header */
|
|
if (fGetProcNams)
|
|
ReadToHeader();
|
|
inmodhdr = FALSE;
|
|
|
|
} /* while !fEof etc. */
|
|
|
|
while (!fEof) {
|
|
ReadLine(fDelete);
|
|
} /* read rest of file */
|
|
|
|
if (fInHeader) {
|
|
/* Error: reached EOF with unterminated header */
|
|
fInHeader = FALSE;
|
|
fprintf(stderr,
|
|
"Error: function %s not terminated before end-of-file in %s\n",
|
|
entry[nentries].entname, szFilenameCur);
|
|
}
|
|
|
|
fclose(ifd);
|
|
fclose(cfd);
|
|
|
|
} /* MyReadFile */
|
|
|
|
|
|
/***
|
|
*PrintSep() - print a seperator
|
|
*
|
|
*Purpose:
|
|
* Output a some lines which mark function header boundaries.
|
|
*
|
|
***************************************************************************/
|
|
|
|
void PrintSep(void)
|
|
{
|
|
printf("---------------------------------------");
|
|
printf("----------------------------------------\n");
|
|
} /* PrintSep */
|
|
|
|
|
|
/***
|
|
*PrintEntry(i) - print out an entry point header/module header
|
|
*
|
|
*Purpose:
|
|
* Prints the given header out
|
|
*
|
|
***************************************************************************/
|
|
|
|
PrintEntry(i)
|
|
int i;
|
|
{
|
|
int linecnt, m, entrysize;
|
|
if ((m = entry[i].funcid) >= 0)
|
|
entrysize = 4 + func[m].nlines; /* primary entry point */
|
|
else
|
|
entrysize = 4; /* secondary entry point */
|
|
if (outline + entrysize > PAGELEN && outline > 1) {
|
|
while (outline > PAGELEN)
|
|
outline -= PAGELEN;
|
|
while (outline <= PAGELEN) {
|
|
printf("\n");
|
|
outline++;
|
|
}
|
|
outline = 1;
|
|
}
|
|
printf("\n");
|
|
PrintSep();
|
|
printf("%s - ", entry[i].entname);
|
|
if (m < 0)
|
|
printf("see %s\n", entry[-1-m].entname);
|
|
else
|
|
printf("File: %s\n", func[m].filename);
|
|
PrintSep();
|
|
outline = outline + 4;
|
|
if (m < 0 || func[m].nlines == 0)
|
|
return(0);
|
|
fseek(ifd, func[m].filepos, 0);
|
|
fEof = FALSE;
|
|
linecnt = func[m].nlines;
|
|
ReadLine(FALSE);
|
|
do {
|
|
WriteLine(linebuf, stdout);
|
|
outline++;
|
|
ReadLine(FALSE);
|
|
}
|
|
while ((!fEof) && ((--linecnt) > 0));
|
|
} /* PrintEntry */
|
|
|
|
#if 0
|
|
/***************************************************************************
|
|
*_stricmp(pbLeft, pbRight) - case insensitive string compare
|
|
*
|
|
*Purpose:
|
|
* Case-insensitive string comparison.
|
|
*
|
|
*Entry:
|
|
* pbLeft, pbRight = ptrs to strings to compare
|
|
*
|
|
*Exit:
|
|
* Return 0 if left string == right string, -1 if left string less than
|
|
* right string, 1 otherwise
|
|
*
|
|
*NOTE:
|
|
* This routine is provided because not all C runtime libraries support
|
|
* this; specifically, MS C for DOS does have this routine, but
|
|
* on a 68k it doesn't seem to be there.
|
|
*
|
|
***************************************************************************/
|
|
char *malloc();
|
|
#define isLcase(c) (((c) >= 'a') && ((c) <= 'z'))
|
|
#define upit(c) ((isLcase(c))? (c) - 'a' + 'A' : (c))
|
|
|
|
_stricmp(pbLeft, pbRight)
|
|
char *pbLeft, *pbRight;
|
|
{
|
|
int cbLeft = strlen(pbLeft);
|
|
int cbRight = strlen(pbRight);
|
|
char *pbUCleft = malloc(cbLeft +1);
|
|
char *pbUCright = malloc(cbRight +1);
|
|
register char *pbSrc;
|
|
register char *pbDst;
|
|
register int i, retval;
|
|
|
|
pbDst = pbUCleft;
|
|
pbSrc = pbLeft;
|
|
for (i = 0; i <= cbLeft; i++, pbDst++, pbSrc++)
|
|
*pbDst = (char)upit(*pbSrc);
|
|
|
|
pbDst = pbUCright;
|
|
pbSrc = pbRight;
|
|
for (i = 0; i <= cbRight; i++, pbDst++, pbSrc++)
|
|
*pbDst = (char)upit(*pbSrc);
|
|
|
|
retval = strcmp(pbUCleft, pbUCright);
|
|
|
|
free(pbUCright);
|
|
free(pbUCleft);
|
|
|
|
return(retval);
|
|
} /* strless */
|
|
|
|
#endif /* 0 */
|
|
|
|
/***************************************************************************
|
|
* SortFunctions()
|
|
*
|
|
* Purpose:
|
|
* Print sorted list of functions headers.
|
|
*
|
|
***************************************************************************/
|
|
void SortFunctions(void)
|
|
{
|
|
int i, j, low;
|
|
ifd = fopen("temp", "r");
|
|
i = -1;
|
|
while (++i <= nentries) {
|
|
low = 0;
|
|
j = -1;
|
|
while (++j <= nentries) {
|
|
if (!entry[j].printed) {
|
|
if (entry[low].printed)
|
|
low = j;
|
|
else if (0 > _stricmp(entry[j].entname, entry[low].entname))
|
|
low = j;
|
|
}
|
|
}
|
|
PrintEntry(low);
|
|
entry[low].printed = TRUE;
|
|
}
|
|
} /* SortFunctions */
|
|
|
|
/***
|
|
*UsageError() - print out usage
|
|
*Purpose:
|
|
* prints out the usage guidelines
|
|
*
|
|
*Entry:
|
|
*
|
|
*Exit:
|
|
* exits to DOS
|
|
*
|
|
*Exceptions:
|
|
*
|
|
*******************************************************************************/
|
|
|
|
void UsageError(void)
|
|
{
|
|
printf("Usage: striphdr [switches] file {file ...}\n\n");
|
|
printf(" Accepts native code or C source; determines source\n");
|
|
printf(" type based on filename suffix.\n");
|
|
printf(" Wildcards may be used in the file names.\n");
|
|
printf("\n");
|
|
printf("Switches:\n");
|
|
printf(" -m process module headers only\n");
|
|
printf(" -b processes both procedure and module headers\n");
|
|
printf(" [default is procedure headers only]\n");
|
|
printf(" -l processes only first line of each header\n");
|
|
printf(" -n processes none of header (i.e. lists only function name)\n");
|
|
printf(" -s <name> processes named section only (may be used with -l)\n");
|
|
printf(" [default is to process whole header]\n");
|
|
printf(" -d delete processed section from input file\n");
|
|
printf(" -q quiet, do not print headers (useful only with -d)\n");
|
|
printf(" -x <ext> gives extension for output file when -d used\n");
|
|
printf(" [default: .new]\n");
|
|
printf(" -r remove revision histories from file headers\n");
|
|
printf(" (equivalent to -m -d -q -s \"Revision History\")\n");
|
|
exit(-1);
|
|
} /* UsageError */
|
|
|
|
|
|
void gdir( char * dst, char * src)
|
|
{
|
|
int i;
|
|
for ( i = strlen(src) -1; i >= 0 && (src[i] != '\\'); i--);
|
|
strncpy(dst, src, i);
|
|
dst[i] = 0;
|
|
}
|
|
|
|
|
|
|
|
/***
|
|
*main() - parse command line and process all file
|
|
*
|
|
*Purpose:
|
|
* To run the other procedures based on the command line.
|
|
*
|
|
*******************************************************************************/
|
|
|
|
int main(int argc, char *argv[])
|
|
{
|
|
int filei;
|
|
int fScanningSwitches = TRUE;
|
|
char base_dir[256], curr_dir[256];
|
|
long h_find;
|
|
struct _finddata_t f_data;
|
|
|
|
nfuncs = -1;
|
|
nentries = -1;
|
|
nambufi = 0;
|
|
ofpos = 0;
|
|
filei = 1;
|
|
outline = 1;
|
|
|
|
if (argc == 1) /* no args given - print usage message and quit */
|
|
UsageError();
|
|
|
|
while (fScanningSwitches) {
|
|
if (filei >= argc) {
|
|
fScanningSwitches = FALSE;
|
|
}
|
|
else if (_stricmp("-d", argv[filei]) == 0) {
|
|
fDelete = TRUE;
|
|
filei++;
|
|
}
|
|
else if (_stricmp("-q", argv[filei]) == 0) {
|
|
fQuiet = TRUE;
|
|
filei++;
|
|
}
|
|
else if (_stricmp("-n", argv[filei]) == 0) {
|
|
fDoFirst = fDoSection = fDoAll = FALSE;
|
|
filei++;
|
|
}
|
|
else if (_stricmp("-m", argv[filei]) == 0) {
|
|
fGetModNams = TRUE;
|
|
fGetProcNams = FALSE;
|
|
filei++;
|
|
}
|
|
else if (_stricmp("-b", argv[filei]) == 0) {
|
|
fGetModNams = TRUE;
|
|
fGetProcNams = TRUE;
|
|
filei++;
|
|
}
|
|
else if (_stricmp("-l", argv[filei]) == 0) {
|
|
fDoFirst = TRUE;
|
|
fDoAll = FALSE;
|
|
filei++;
|
|
}
|
|
else if (_stricmp("-s", argv[filei]) == 0) {
|
|
fDoSection = TRUE;
|
|
fDoAll = FALSE;
|
|
filei++;
|
|
if (filei >= argc)
|
|
UsageError();
|
|
strcpy(SectionHead, argv[filei]); /* copy section header in */
|
|
filei++;
|
|
}
|
|
else if (_stricmp("-x", argv[filei]) == 0) {
|
|
filei++;
|
|
if (argv[filei][0] == '.')
|
|
strncpy(CopyExt, argv[filei]+1, 3); /* skip '.' */
|
|
else
|
|
strncpy(CopyExt, argv[filei], 3);
|
|
CopyExt[3] = '\0'; /* terminate string */
|
|
filei++;
|
|
}
|
|
else if (_stricmp("-r", argv[filei]) == 0) {
|
|
fDelete = TRUE;
|
|
fGetModNams = TRUE;
|
|
fGetProcNams = FALSE;
|
|
fDoAll = FALSE;
|
|
fDoSection = TRUE;
|
|
fQuiet = TRUE;
|
|
strcpy(SectionHead, "Revision History");
|
|
filei++;
|
|
}
|
|
else if (*(argv[filei]) == '-')
|
|
UsageError();
|
|
else
|
|
fScanningSwitches = FALSE;
|
|
}
|
|
|
|
if (filei >= argc)
|
|
UsageError(); /* no files specified */
|
|
|
|
if (fQuiet)
|
|
ofd = fopen("nul", "w"); /* in quiet mode, so no need to save the headers */
|
|
else
|
|
ofd = fopen("temp", "w");
|
|
if (ofd == NULL)
|
|
MyAbort(FILENO, "temp"); /* can't open file */
|
|
|
|
filei--;
|
|
|
|
if ( _getcwd(base_dir, 255) == NULL)
|
|
exit(0);
|
|
while (++filei < argc) {
|
|
gdir(curr_dir, argv[filei]);
|
|
if (_chdir(curr_dir) == -1) {
|
|
printf("%s: %s\n", curr_dir, strerror(errno));
|
|
exit(0);
|
|
}
|
|
if ( (h_find = _findfirst(argv[filei], &f_data)) == -1)
|
|
continue;
|
|
do
|
|
{
|
|
MyReadFile(f_data.name);
|
|
} while ( _findnext(h_find, &f_data) == 0);
|
|
_findclose(h_find);
|
|
if (_chdir(base_dir) == -1) {
|
|
printf("%s: %s\n", curr_dir, strerror(errno));
|
|
exit(0);
|
|
}
|
|
}
|
|
fclose(ofd);
|
|
if (!fQuiet)
|
|
SortFunctions();
|
|
return 0 ;
|
|
}
|