//$Header$ //{f0d952cc-7499-4d5c-9f46-d0b509c5701c} //------------------------------------------------------------------------------------------------- //Copyright (c) 2018, David T. Ashley // //This file is part of "ifsfscan", a program for identifying gross formatting anomalies in source //files (Windows/ASCII text files only). // //This source code and any program in which it is compiled/used is licensed under the MIT License, //reproduced below. // //Permission is hereby granted, free of charge, to any person obtaining a copy of //this software and associated documentation files(the "Software"), to deal in the //Software without restriction, including without limitation the rights to use, //copy, modify, merge, publish, distribute, sublicense, and / or sell copies of the //Software, and to permit persons to whom the Software is furnished to do so, //subject to the following conditions : // //The above copyright notice and this permission notice shall be included in all //copies or substantial portions of the Software. // //THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR //IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, //FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE //AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER //LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, //OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE //SOFTWARE. //------------------------------------------------------------------------------------------------- #include #include #include #include #include #include #define FCMIOF_HORIZONTAL_BAR_SEP_CHAR ('-') #define FCMIOF_LINE_LEN (78) //The last column allowed for a characters below is Column 82 (or it will be //less than aesthetic). const char * const license_preamble[] = { "ifsfscan, (c) 2018 David T. Ashley (dashley@gmail.com)", "This program's source files, executable files, and all other related files", "(such as Visual Studio project files) are licensed under \"The MIT License\",", "reproduced below." }; const char * const license_text[] = { "Permission is hereby granted, free of charge, to any person obtaining a copy", "of this software and associated documentation files(the \"Software\"), to deal", "in the Software without restriction, including without limitation the rights", "to use, copy, modify, merge, publish, distribute, sublicense, and / or sell", "copies of the Software, and to permit persons to whom the Software is", "furnished to do so, subject to the following conditions:", "", "The above copyright notice and this permission notice shall be included in", "all copies or substantial portions of the Software.", "", "THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR", "IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,", "FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE", "AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER", "LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,", "OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE", "SOFTWARE." }; const char * const prog_desc_text[] = { "ifsfscan (mnemonic: Ill-Formed Source File SCAN) is a program for detecting", "gross formatting errors in source files (Windows/ASCII text files only). The", "errors detected are non-ASCII characters, tab characters, abnormal", "end-of-line characters, and trailing whitespace on lines.", }; const char * const prog_help_text[] = { "Usage: ifsfscan [filename_or_wildcard [filename_or_wildcard [...]]]", "", "Notes:", " (1) : Wildcards (\"*\", \"?\") are processed by Windows, so Windows is", " the arbiter of which wildcards are accepted and how they expand.", " (2) : This program never writes to a file, so it cannot destroy data", " (except, possibly, by stdout output redirected to a file).", " (3) : This program accepts no options (like \"-help\" or \"-verbose\").", " (4) : This program accepts only Windows line endings (13-10).", " This program is incompatible with *nix and *nix files.", " (5) : This program accepts only the ASCII character set (it will not", " process UTF-8 or any other encodings).", }; //-------------------------------------------------------------------------------- // T E R M I N A T I O N F U N C T I O N S //-------------------------------------------------------------------------------- void CCMFATAL_fatal(const char *desc, const char *file, size_t line) { printf("\n"); printf("Fatal error. Must terminate execution.\n"); printf("File: %s, Line: %zu.\n", file, line); printf("Error description: %s\n", desc); exit(4); //Error code 4 for error termination. } //-------------------------------------------------------------------------------- // A S S E R T I O N F U N C T I O N S //-------------------------------------------------------------------------------- void USERASSERT_assert(int assertion, const char *file, size_t line) { if (! assertion) { printf("\n"); printf("Assertion failed. It is necessary to use the source code to diagnose\n"); printf("and resolve this error.\n"); printf("File: %s, Line: %zu.\n", file, line); exit(4); //Error code 4 for error termination. } } //-------------------------------------------------------------------------------- // M E M O R Y A L L O C A T I O N F U N C T I O N S //-------------------------------------------------------------------------------- //These functions form a layer over the standard library so that conditions of //concern can be more easily trapped. //-------------------------------------------------------------------------------- void *CCMALLOC_malloc(size_t size) { void *rv; rv = malloc(size); if (!rv) { CCMFATAL_fatal("NULL pointer from malloc()--probable out of memory.", __FILE__, __LINE__); } memset(rv, 0, size); return(rv); } void *CCMALLOC_calloc(size_t num, size_t size) { void *rv; rv = calloc(num, size); if (!rv) { CCMFATAL_fatal("NULL pointer from calloc()--probable out of memory.", __FILE__, __LINE__); } memset(rv, 0, size); return(rv); } void *CCMALLOC_realloc(void *memblock, size_t size) { void *rv; rv = realloc(memblock, size); if ((!rv) && (size)) { CCMFATAL_fatal("NULL pointer from realloc()--probable out of memory.", __FILE__, __LINE__); } return(rv); } void CCMALLOC_free(void *memblock) { free(memblock); } //-------------------------------------------------------------------------------- // C H A R A C T E R F U N C T I O N S //-------------------------------------------------------------------------------- int CHARFUNC_digit_to_val(char digit) { switch (digit) { case '0': return(0); break; case '1': return(1); break; case '2': return(2); break; case '3': return(3); break; case '4': return(4); break; case '5': return(5); break; case '6': return(6); break; case '7': return(7); break; case '8': return(8); break; case '9': return(9); break; default: return(-1); break; } } char CHARFUNC_nibble_to_lc_hex_digit(int nibble) { switch (nibble & 0x0F) { case 0: return('0'); break; case 1: return('1'); break; case 2: return('2'); break; case 3: return('3'); break; case 4: return('4'); break; case 5: return('5'); break; case 6: return('6'); break; case 7: return('7'); break; case 8: return('8'); break; case 9: return('9'); break; case 10: return('a'); break; case 11: return('b'); break; case 12: return('c'); break; case 13: return('d'); break; case 14: return('e'); break; case 15: return('f'); break; default: USERASSERT_assert(0, __FILE__, __LINE__); return('?'); break; } } void CHARFUNC_int_to_lc_hex_rev(int arg, char *s) { int i; USERASSERT_assert(s != NULL, __FILE__, __LINE__); for (i = 0; i<8; i++) { s[i] = CHARFUNC_nibble_to_lc_hex_digit(arg); arg >>= 4; } } //-------------------------------------------------------------------------------- // S T R I N G A N D C H A R A C T E R A R R A Y F U N C T I O N S //-------------------------------------------------------------------------------- int STRING_contains_wildcard(const char *s) { if (strchr(s, '?') != NULL) return(1); else if (strchr(s, '*') != NULL) return(1); else return(0); } int STRING_is_longer_than_maxpath(const char *s) { if (_MAX_PATH <= 5) return(1); else if (strlen(s) > (_MAX_PATH - 5)) return(1); else return(0); } int STRING_contains_terminating_backslash(const char *s) { size_t i; i = strlen(s); if (i == 0) { return(0); } else { do { i--; if (s[i] == '\\') return(1); else if ((s[i] != ' ') && (s[i] != '\t')) return(0); } while (i != 0); return(0); } } const char *STRING_vcinfo(size_t which) { static const char * const vcinfo[] = { "$HeadURL$", "$Date$", "$Revision$", "$Author$", "Project GUID: {f7d7586a-557c-43cb-bec5-b49765d96a5d}", "c_main.c GUID: {f0d952cc-7499-4d5c-9f46-d0b509c5701c}", "ifsfscan.cpp GUID: {2abd4437-101c-49eb-99ac-c1174f55b626}", }; if (which < (sizeof(vcinfo) / sizeof(vcinfo[0]))) return(vcinfo[which]); else return(NULL); } //-------------------------------------------------------------------------------- // F O R M A T T E D O U T P U T F U N C T I O N S //-------------------------------------------------------------------------------- int FCMIOF_get_line_len(void) { return(FCMIOF_LINE_LEN); } void FCMIOF_stream_repchar(FILE *s, char c, unsigned n) { USERASSERT_assert(s != NULL, __FILE__, __LINE__); while (n--) fprintf(s, "%c", c); } void FCMIOF_repchar(char c, unsigned n) { while (n--) printf("%c", c); } void FCMIOF_hline(void) { FCMIOF_repchar(FCMIOF_HORIZONTAL_BAR_SEP_CHAR, FCMIOF_LINE_LEN); printf("\n"); } void FCMIOF_stream_hline(FILE *s) { USERASSERT_assert(s != NULL, __FILE__, __LINE__); FCMIOF_stream_repchar(s, FCMIOF_HORIZONTAL_BAR_SEP_CHAR, FCMIOF_LINE_LEN); fprintf(s, "\n"); } void FCMIOF_stream_bannerheading(FILE *f, char *s, int n_extra_lines) { const int lr_padding = 3; /* The number of spaces on each side of what is printed. */ int i; /* General iteration variable. */ int n_asterisks; int input_arg_len; int n_left_spaces; int n_right_spaces; /* Check the file pointer, string pointer, and other par. */ USERASSERT_assert(f != NULL, __FILE__, __LINE__); USERASSERT_assert(s != NULL, __FILE__, __LINE__); USERASSERT_assert(n_extra_lines >= 0, __FILE__, __LINE__); /* Print the right number of solid lines of asterisks to the ** standard output. */ for (i = 0; i (FCMIOF_LINE_LEN - 2 * lr_padding - 2)) input_arg_len = FCMIOF_LINE_LEN - 2 * lr_padding - 2; n_asterisks = (FCMIOF_LINE_LEN - 2 * lr_padding - input_arg_len) / 2; n_left_spaces = lr_padding; if ((FCMIOF_LINE_LEN - 2 * lr_padding - input_arg_len) % 2) { /* Odd, need to pad the right by one. */ n_right_spaces = lr_padding + 1; } else { n_right_spaces = lr_padding; } /* Print the text. */ FCMIOF_stream_repchar(f, '*', n_asterisks); FCMIOF_stream_repchar(f, ' ', n_left_spaces); for (i = 0; i= 0, __FILE__, __LINE__); FCMIOF_stream_bannerheading(stdout, s, n_extra_lines); } void FCMIOF_time_stream(FILE *s, time_t ltime) { char *p; USERASSERT_assert(s != NULL, __FILE__, __LINE__); time(<ime); p = ctime(<ime); if (p) { int i; for (i = 11; i<19; i++) fprintf(s, "%c", p[i]); fprintf(s, " "); for (i = 0; i<10; i++) fprintf(s, "%c", p[i]); fprintf(s, " "); for (i = 20; i<24; i++) fprintf(s, "%c", p[i]); } else { fprintf(s, "??? ??? ?? ??:??:?? ????"); } } int is_legal_non_eol_character(char c) { if ((c >= 32) && (c <= 126)) return(1); else return(0); } int is_illegal_non_eol_character(char c) { if (((c < 32) || (c > 126)) && (c != 13) && (c != 10)) return(1); else return(0); } int is_tab(char c) { if (c == 9) return(1); else return(0); } int is_cr(char c) { if (c == 13) return(1); else return(0); } int is_lf(char c) { if (c == 10) return(1); else return(0); } void emit_human_friendly_llu(unsigned long long arg) { printf("%llu", arg); } void emit_file_pos_3tuple(unsigned long long line, unsigned long long col, unsigned long long offset) { printf(" Line: "); emit_human_friendly_llu(line); printf(", Col: "); emit_human_friendly_llu(col); printf(", Offset: "); emit_human_friendly_llu(offset); printf("\n"); } void process_opened_handle(FILE *f) { unsigned long long char_no; unsigned long long line_no; unsigned long long col_no; enum {PST_LINE, PST_CR_FOUND, PST_LF_FOUND} state = PST_LINE; int exit_flag = 0; char in_c, prev_c; unsigned char in_uc; int in_i; in_i = fgetc(f); if (in_i == EOF) { //Zero-length file or error. Because this tool isn't critical, no need to figure out which, exactly. printf(" Zero-length file.\n"); return; } prev_c = ' '; char_no = 0; line_no = 1; col_no = 1; in_c = in_i & 0xFF; in_uc = in_i & 0xFF; do { //Illegal characters always get flagged. if (is_illegal_non_eol_character(in_c)) { emit_file_pos_3tuple(line_no, col_no, char_no); printf(" Illegal character: 0x%02x.\n", ((unsigned)in_uc)); } //Run through the state machine, which would look for bad EOL sequences. switch (state) { case PST_LINE: if (is_lf(in_c)) { //Line feeds not allowed without preceding carriage return. emit_file_pos_3tuple(line_no, col_no, char_no); printf(" Out of sequence line feed character (0x0a)\n"); line_no++; col_no = 1; state = PST_LF_FOUND; } else if (is_cr(in_c)) { //Legal state = PST_CR_FOUND; } else { //Ordinary character. col_no++; } break; case PST_CR_FOUND: if (is_lf(in_c)) { //Legal line_no++; col_no = 1; state = PST_LF_FOUND; } else if (is_cr(in_c)) { //Back-to-back carriage returns not allowed. emit_file_pos_3tuple(line_no, col_no, char_no); printf(" Out of sequence carriage return character (0x0D)\n"); col_no++; } else { //Ordinary character. Illegal, because LF must follow CR. emit_file_pos_3tuple(line_no, col_no, char_no); printf(" Carriage return followed by 0x%02x rather than LF.\n", (unsigned)in_uc); col_no++; } break; case PST_LF_FOUND: if (is_lf(in_c)) { //Illegal. Back-to-back line feeds not allowed. emit_file_pos_3tuple(line_no, col_no, char_no); printf(" Out of sequence line feed character (0x0A).\n"); line_no++; col_no = 1; } else if (is_cr(in_c)) { //Legal. Blank lines are fine. col_no++; state = PST_LF_FOUND; } else { //Ordinary character. Legal. col_no++; state = PST_LINE; } break; default: USERASSERT_assert(0, __FILE__, __LINE__); break; } in_i = fgetc(f); char_no++; if (in_i == EOF) exit_flag = 1; in_c = in_i & 0xff; in_uc = in_i & 0xff; } while (!exit_flag); } void process_file_by_name(const char *s) { FILE *f; printf(" %s\n", s); f = fopen(s, "rb"); if (!f) { printf(" fopen() failed.\n"); } else { process_opened_handle(f); if (fclose(f)) { printf(" fclose() failed.\n"); } } } void process_filename_or_wildcard(const char *fname_or_wildcard) { HANDLE hFind; WIN32_FIND_DATA FindFileData; //Incoming pointer is worthy of an assertion. The OS should not every deliver //a NULL pointer or empty string to us. USERASSERT_assert(fname_or_wildcard != NULL, __FILE__, __LINE__); USERASSERT_assert(strlen(fname_or_wildcard) > 0, __FILE__, __LINE__); printf("%s\n", fname_or_wildcard); if (STRING_is_longer_than_maxpath(fname_or_wildcard)) { printf(" Specified filename or wildcard too long--cannot process.\n"); } else if (STRING_contains_terminating_backslash(fname_or_wildcard)) { printf(" Specified filename or wildcard contains terminating \"\\\"--cannot process.\n"); } else if (STRING_contains_wildcard(fname_or_wildcard)) { hFind = FindFirstFile((TCHAR *)fname_or_wildcard, &FindFileData); if (hFind == INVALID_HANDLE_VALUE) { printf(" Wildcard does not match existing files or is invalid.\n"); } else { char path_drive[_MAX_PATH + 5]; char path_dir[_MAX_PATH + 5]; char path_fname[_MAX_PATH + 5]; char path_ext[_MAX_PATH + 5]; char path_full[_MAX_PATH + 5]; if (!(FindFileData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)) { _splitpath(fname_or_wildcard, path_drive, path_dir, path_fname, path_ext); strcpy(path_full, path_drive); strcat(path_full, path_dir); strcat(path_full, FindFileData.cFileName); process_file_by_name(path_full); } while (FindNextFile(hFind, &FindFileData) != 0) { if (!(FindFileData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)) { _splitpath(fname_or_wildcard, path_drive, path_dir, path_fname, path_ext); strcpy(path_full, path_drive); strcat(path_full, path_dir); strcat(path_full, FindFileData.cFileName); process_file_by_name(path_full); } } } if (hFind != INVALID_HANDLE_VALUE) { FindClose(hFind); } } else { process_file_by_name(fname_or_wildcard); } } void emit_no_par_documentation(void) { size_t i; FCMIOF_stream_hline(stdout); for (i = 0; i < (sizeof(prog_desc_text) / sizeof(prog_desc_text[0])); i++) printf("%s\n", prog_desc_text[i]); FCMIOF_stream_hline(stdout); for (i = 0; i < (sizeof(license_preamble) / sizeof(license_preamble[0])); i++) printf("%s\n", license_preamble[i]); FCMIOF_stream_hline(stdout); for (i = 0; i < (sizeof(license_text) / sizeof(license_text[0])); i++) printf("%s\n", license_text[i]); FCMIOF_stream_hline(stdout); printf("Program built on %s at %s.\n", __DATE__, __TIME__); i = 0; while (STRING_vcinfo(i)) { printf("%s\n", STRING_vcinfo(i)); i++; } FCMIOF_stream_hline(stdout); for (i = 0; i < (sizeof(prog_help_text) / sizeof(prog_help_text[0])); i++) printf("%s\n", prog_help_text[i]); FCMIOF_stream_hline(stdout); } void emit_execution_preamble(void) { FCMIOF_stream_hline(stdout); printf("Use \"ifsfscan\" with no parameters to obtain license and help information.\n"); FCMIOF_stream_hline(stdout); } int c_main(int argc, char **argv) { int i; if (argc <= 1) { //This is most likely someone trying to figure out what the program is or does. //Spit everything. emit_no_par_documentation(); } else { emit_execution_preamble(); //Every argument beyond the program name has to be either a file name or //wildcard. Just process them in order. for (i = 1; i < argc; i++) process_filename_or_wildcard(argv[i]); FCMIOF_stream_hline(stdout); } return 0; }