nuttx/tools/nxstyle.c
2019-02-27 14:02:53 -06:00

1308 lines
44 KiB
C

/****************************************************************************
* tools/nxstyle.c
*
* Copyright (C) 2015, 2018 Gregory Nutt. All rights reserved.
* Author: Gregory Nutt <gnutt@nuttx.org>
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in
* the documentation and/or other materials provided with the
* distribution.
* 3. Neither the name NuttX nor the names of its contributors may be
* used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
* COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
* OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
* AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
* ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*
****************************************************************************/
/****************************************************************************
* Included Files
****************************************************************************/
#include <stdlib.h>
#include <stdbool.h>
#include <stdio.h>
#include <string.h>
#include <strings.h>
#include <ctype.h>
/****************************************************************************
* Pre-processor Definitions
****************************************************************************/
#define LINE_SIZE 512
/****************************************************************************
* Private Functions
****************************************************************************/
static void show_usage(char *progname, int exitcode)
{
fprintf(stderr, "Usage: %s [-m <maxline>] <filename>\n", progname);
fprintf(stderr, " %s -h\n", progname);
exit(exitcode);
}
/****************************************************************************
* Public Functions
****************************************************************************/
int main(int argc, char **argv, char **envp)
{
FILE *instream; /* File input stream */
char line[LINE_SIZE]; /* The current line being examined */
char *filename; /* Name of the file to open */
char *lptr; /* Temporary pointer into line[] */
bool btabs; /* True: TAB characters found on the line */
bool bfunctions; /* True: In private or public functions */
bool bstatm; /* True: This line is beginning of a statement */
bool bfor; /* True: This line is beginning of a 'for' statement */
bool bswitch; /* True: Within a switch statement */
bool bstring; /* True: Within a string */
bool bquote; /* True: Backslash quoted character next */
bool bblank; /* Used to verify block comment termintor */
bool ppline; /* True: The next line the continuation of a precessor command */
int lineno; /* Current line number */
int indent; /* Indentation level */
int ncomment; /* Comment nesting level on this line */
int prevncomment; /* Comment nesting level on the previous line */
int bnest; /* Brace nesting level on this line */
int prevbnest; /* Brace nesting level on the previous line */
int dnest; /* Data declaration nesting level on this line */
int prevdnest; /* Data declaration nesting level on the previous line */
int pnest; /* Parenthesis nesting level on this line */
int comment_lineno; /* Line on which the last one line comment was closed */
int blank_lineno; /* Line number of the last blank line */
int noblank_lineno; /* A blank line is not needed after this line */
int linelen; /* Length of the line */
int maxline; /* Lines longer that this generate warnings */
int n;
int i;
maxline = 78;
filename = argv[1];
/* Usage: nxstyle [-m <maxline>] <filename>
* nxstyle -h
*/
if (argc == 4)
{
if (strcmp(argv[1], "-m") != 0)
{
fprintf(stderr, "Unrecognized argument\n");
show_usage(argv[0], 1);
}
maxline = atoi(argv[2]);
if (maxline < 1)
{
fprintf(stderr, "Bad value for <maxline>\n");
show_usage(argv[0], 1);
}
filename = argv[3];
}
else if (argc == 2)
{
if (strcmp(argv[1], "-h") == 0)
{
show_usage(argv[0], 0);
}
}
else
{
fprintf(stderr, "Invalid number of arguments\n");
show_usage(argv[0], 1);
}
instream = fopen(filename, "r");
if (!instream)
{
fprintf(stderr, "Failed to open %s\n", argv[1]);
return 1;
}
btabs = false; /* True: TAB characters found on the line */
bfunctions = false; /* True: In private or public functions */
bswitch = false; /* True: Within a switch statement */
bstring = false; /* True: Within a string */
ppline = false; /* True: Continuation of a pre-processor line */
lineno = 0; /* Current line number */
ncomment = 0; /* Comment nesting level on this line */
bnest = 0; /* Brace nesting level on this line */
dnest = 0; /* Data declaration nesting level on this line */
pnest = 0; /* Parenthesis nesting level on this line */
comment_lineno = -1; /* Line on which the last one line comment was closed */
blank_lineno = -1; /* Line number of the last blank line */
noblank_lineno = -1; /* A blank line is not needed after this line */
while (fgets(line, LINE_SIZE, instream))
{
lineno++;
indent = 0;
prevbnest = bnest; /* Brace nesting level on the previous line */
prevdnest = dnest; /* Data declaration nesting level on the previous line */
prevncomment = ncomment; /* Comment nesting level on the previous line */
bstatm = false; /* True: This line is beginning of a statement */
bfor = false; /* REVISIT: Implies for() is all on one line */
/* Check for a blank line */
for (n = 0; line[n] != '\n' && isspace((int)line[n]); n++)
{
}
if (line[n] == '\n')
{
if (n > 0)
{
fprintf(stderr, "Blank line contains whitespace at line %d\n",
lineno);
}
if (lineno == 1)
{
fprintf(stderr, "File begins with a blank line\n");
}
else if (lineno == blank_lineno + 1)
{
fprintf(stderr, "Too many blank lines at line %d\n", lineno);
}
blank_lineno = lineno;
continue;
}
else /* This line is non-blank */
{
/* Check for a missing blank line after a comment */
if (lineno == comment_lineno + 1)
{
/* No blank line should be present if the current line contains
* a right brace, a pre-processor line, the start of another
* comment.
*
* REVISIT: Generates a false alarm if the current line is also
* a comment. Generally it is acceptable for one comment to
* follow another with no space separation.
*/
if (line[n] != '}' /* && line[n] != '#' */)
{
fprintf(stderr,
"Missing blank line after comment line. Found at line %d\n",
comment_lineno);
}
}
/* Files must begin with a comment (the file header).
* REVISIT: Logically, this belongs in the STEP 2 operations
* below.
*/
if (lineno == 1 && (line[n] != '/' || line[n + 1] != '*'))
{
fprintf(stderr,
"Missing file header comment block at line 1\n");
}
}
/* STEP 1: Find the indentation level and the start of real stuff on
* the line.
*/
for (n = 0; line[n] != '\n' && isspace((int)line[n]); n++)
{
switch (line[n])
{
case ' ':
{
indent++;
}
break;
case '\t':
{
if (!btabs)
{
fprintf(stderr, "TABs found. First at line %d:%d\n", lineno, n);
btabs = true;
}
indent = (indent + 4) & ~3;
}
break;
default:
{
fprintf(stderr,
"Unexpected white space character %02x found at line %d:%d\n",
line[n], lineno, n);
}
break;
}
}
/* STEP 2: Detect some certain start of line conditions */
/* Skip over pre-processor lines (or continuations of pre-processor
* lines as indicated by ppline)
*/
if (line[indent] == '#' || ppline)
{
int len;
/* Suppress error for comment following conditional compilation */
noblank_lineno = lineno;
/* Check if the next line will be a continuation of the pre-
* processor command.
*/
len = strlen(&line[indent]) + indent - 1;
if (line[len] == '\n')
{
len--;
}
ppline = (line[len] == '\\');
continue;
}
/* Check for a single line comment */
linelen = strlen(line);
if (linelen >= 5) /* Minimum is slash, star, star, slash, newline */
{
lptr = strstr(line, "*/");
if (line[indent] == '/' && line[indent +1] == '*' &&
lptr - line == linelen - 3)
{
/* Check if there should be a blank line before the comment */
if (lineno > 1 &&
comment_lineno != lineno - 1 &&
blank_lineno != lineno - 1 &&
noblank_lineno != lineno - 1)
{
/* TODO: This generates a false alarm if preceded by a label. */
fprintf(stderr,
"Missing blank line before comment found at line %d\n",
lineno);
}
comment_lineno = lineno;
}
}
/* Check for the comment block indicating the beginning of functions. */
if (!bfunctions && ncomment > 0 &&
(strcmp(line, " * Private Functions\n") == 0 ||
strcmp(line, " * Public Functions\n") == 0))
{
//fprintf(stderr, "Functions begin at line %d:%d\n", lineno, n);
bfunctions = true;
}
/* Check for some kind of declaration.
* REVISIT: The following logic fails for any non-standard types.
* REVISIT: Terminator after keyword might not be a space. Might be
* a newline, for example. struct and unions are often unnamed, for
* example.
*/
else if (strncmp(&line[indent], "auto ", 5) == 0 ||
strncmp(&line[indent], "bool ", 5) == 0 ||
strncmp(&line[indent], "char ", 5) == 0 ||
strncmp(&line[indent], "CODE ", 5) == 0 ||
strncmp(&line[indent], "const ", 6) == 0 ||
strncmp(&line[indent], "double ", 7) == 0 ||
// strncmp(&line[indent], "struct ", 7) == 0 ||
strncmp(&line[indent], "struct", 6) == 0 || /* May be unnamed */
strncmp(&line[indent], "enum ", 5) == 0 ||
strncmp(&line[indent], "extern ", 7) == 0 ||
strncmp(&line[indent], "EXTERN ", 7) == 0 ||
strncmp(&line[indent], "FAR ", 4) == 0 ||
strncmp(&line[indent], "float ", 6) == 0 ||
strncmp(&line[indent], "int ", 4) == 0 ||
strncmp(&line[indent], "int16_t ", 8) == 0 ||
strncmp(&line[indent], "int32_t ", 8) == 0 ||
strncmp(&line[indent], "long ", 5) == 0 ||
strncmp(&line[indent], "off_t ", 6) == 0 ||
strncmp(&line[indent], "register ", 9) == 0 ||
strncmp(&line[indent], "short ", 6) == 0 ||
strncmp(&line[indent], "signed ", 7) == 0 ||
strncmp(&line[indent], "size_t ", 7) == 0 ||
strncmp(&line[indent], "ssize_t ", 8) == 0 ||
strncmp(&line[indent], "static ", 7) == 0 ||
strncmp(&line[indent], "time_t ", 7) == 0 ||
strncmp(&line[indent], "typedef ", 8) == 0 ||
strncmp(&line[indent], "uint8_t ", 8) == 0 ||
strncmp(&line[indent], "uint16_t ", 9) == 0 ||
strncmp(&line[indent], "uint32_t ", 9) == 0 ||
// strncmp(&line[indent], "union ", 6) == 0 ||
strncmp(&line[indent], "union", 5) == 0 || /* May be unnamed */
strncmp(&line[indent], "unsigned ", 9) == 0 ||
strncmp(&line[indent], "void ", 5) == 0 ||
strncmp(&line[indent], "volatile ", 9) == 0)
{
/* REVISIT: Also picks up function return types */
/* REVISIT: Logic problem for nested data/function declarations */
if ((!bfunctions || bnest > 0) && dnest == 0)
{
dnest = 1;
}
/* Check for multiple definitions of variables on the line.
* Ignores declarations within parentheses which are probably
* formal parameters.
*/
if (pnest == 0)
{
int tmppnest;
/* Note, we have not yet parsed each character on the line so
* a comma have have been be preceded by '(' on the same line.
* We will have parse up to any comma to see if that is the
* case.
*/
for (i = indent, tmppnest = 0;
line[i] != '\n' && line[i] != '\0';
i++)
{
if (tmppnest == 0 && line[i] == ',')
{
fprintf(stderr,
"Multiple data definitions on line %d\n",
lineno);
break;
}
else if (line[i] == '(')
{
tmppnest++;
}
else if (line[i] == ')')
{
if (tmppnest < 1)
{
/* We should catch this later */
break;
}
tmppnest--;
}
else if (line[i] == ';')
{
/* Break out if the semicolon terminates the
* declaration is found. Avoids processing any
* righthand comments in most cases.
*/
break;
}
}
}
}
/* Check for a keyword indicating the beginning of a statement.
* REVISIT: This, obviously, will not detect statements that do not
* begin with a C keyword (such as assignment statements).
*/
else if (strncmp(&line[indent], "break ", 6) == 0 ||
strncmp(&line[indent], "case ", 5) == 0 ||
// strncmp(&line[indent], "case ", 5) == 0 || /* Part of switch */
strncmp(&line[indent], "continue ", 9) == 0 ||
// strncmp(&line[indent], "default ", 8) == 0 || /* Part of switch */
strncmp(&line[indent], "do ", 3) == 0 ||
strncmp(&line[indent], "else ", 5) == 0 ||
strncmp(&line[indent], "goto ", 5) == 0 ||
strncmp(&line[indent], "if ", 3) == 0 ||
strncmp(&line[indent], "return ", 7) == 0 ||
// strncmp(&line[indent], "switch ", 7) == 0 || /* Doesn't follow pattern */
strncmp(&line[indent], "while ", 6) == 0)
{
bstatm = true;
}
/* Spacing works a little differently for and switch statements */
else if (strncmp(&line[indent], "for ", 4) == 0)
{
bfor = true;
bstatm = true;
}
else if (strncmp(&line[indent], "switch ", 7) == 0)
{
bswitch = true;
}
/* Also check for C keywords with missing white space */
else if (strncmp(&line[indent], "do(", 3) == 0 ||
strncmp(&line[indent], "if(", 3) == 0 ||
strncmp(&line[indent], "while(", 6) == 0)
{
fprintf(stderr, "Missing whitespace after keyword at line %d:%d\n", lineno, n);
bstatm = true;
}
else if (strncmp(&line[indent], "for(", 4) == 0)
{
fprintf(stderr, "Missing whitespace after keyword at line %d:%d\n", lineno, n);
bfor = true;
bstatm = true;
}
else if (strncmp(&line[indent], "switch(", 7) == 0)
{
fprintf(stderr, "Missing whitespace after keyword at line %d:%d\n", lineno, n);
bswitch = true;
}
/* STEP 3: Parse each character on the line */
bquote = false; /* True: Backslash quoted character next */
bblank = true; /* Used to verify block comment termintor */
for (; line[n] != '\n' && line[n] != '\0'; n++)
{
if (line[n] == '/' && !bstring)
{
/* Check for start of a C comment */
if (line[n + 1] == '*')
{
if (line[n + 2] == '\n')
{
fprintf(stderr, "C comment on separate line at %d:%d\n",
lineno, n);
}
else if (line[n + 2] != ' ' && line[n + 2] != '*')
{
fprintf(stderr,
"Missing space after opening C comment at line %d:%d\n",
lineno, n);
}
ncomment++;
n++;
continue;
}
/* Check for end of a C comment */
else if (n > 0 && line[n - 1] == '*')
{
if (n < 2)
{
fprintf(stderr, "Closing C comment not indented at line %d:%d\n",
lineno, n);
}
else if (line[n - 2] != ' ' && line[n - 2] != '*')
{
fprintf(stderr,
"Missing space before closing C comment at line %d:%d\n",
lineno, n);
}
/* Check for block comments that are not on a separate line.
* This would be the case if we are we are within a comment
* that did not start on this line and the current line is
* not blank up to the point where the comment was closed.
*/
if (prevncomment > 0 && !bblank)
{
fprintf(stderr,
"Block comment terminator must be on a separate line at line %d:%d\n",
lineno, n);
}
#if 0
/* REVISIT: Generates false alarms when portions of an
* expression are commented out within the expression.
*/
if (line[n + 1] != '\n')
{
fprintf(stderr,
"Garbage on line after C comment at line %d:%d\n",
lineno, n);
}
#endif
/* Handle nested comments */
if (ncomment > 0)
{
/* Remember the line number of the line containing the
* closing of the outermost comment.
*/
if (--ncomment == 0)
{
#if 0
/* REVISIT: causes false alarms when comment appears to
* the right of a statement.
*/
comment_lineno = lineno;
#endif
}
}
else
{
ncomment = 0;
fprintf(stderr,
"Closing without opening comment at line %d:%d\n",
lineno, n);
}
n++;
continue;
}
/* Check for C++ style comments
* NOTE: Gives false alarms on URLs (http://...) embedded
* inside of comments.
*/
else if (line[n + 1] == '/')
{
fprintf(stderr, "C++ style comment on at %d:%d\n",
lineno, n);
n++;
continue;
}
}
/* Check if the line is blank so far. This is only used to
* to verify the the closing of a block comment is on a separate
* line. So we also need to treat '*' as a 'blank'.
*/
if (!isblank(line[n]) && line[n] != '*')
{
bblank = false;
}
/* Check for a string... ignore if we are in the middle of a
* comment.
*/
if (ncomment == 0)
{
/* Backslash quoted charater */
if (line[n] == '\\')
{
bquote = true;
n++;
}
/* Check for quoated characters: \" in string */
if (line[n] == '"' && !bquote)
{
bstring = !bstring;
}
bquote = false;
}
/* The reset of the line is only examined of we are not in a comment
* or a string.
*
* REVISIT: Should still check for whitespace at the end of the
* line.
*/
if (ncomment == 0 && !bstring)
{
switch (line[n])
{
/* Handle logic nested with curly braces */
case '{':
{
if (n > indent)
{
if (dnest == 0)
{
fprintf(stderr,
"Left bracket not on separate line at %d:%d\n",
lineno, n);
}
}
else if (line[n + 1] != '\n')
{
if (dnest == 0)
{
fprintf(stderr,
"Garbage follows left bracket at line %d:%d\n",
lineno, n);
}
}
bnest++;
if (dnest > 0)
{
dnest++;
}
/* Suppress error for comment following a left brace */
noblank_lineno = lineno;
}
break;
case '}':
{
if (bnest < 1)
{
fprintf(stderr, "Unmatched right brace at line %d:%d\n", lineno, n);
}
else
{
bnest--;
if (bnest < 1)
{
bnest = 0;
bswitch = false;
}
}
if (dnest < 3)
{
dnest = 0;
}
else
{
dnest--;
}
if (n > indent)
{
if (dnest == 0)
{
fprintf(stderr,
"Right bracket not on separate line at %d:%d\n",
lineno, n);
}
}
else if (line[n + 1] != '\n' &&
line[n + 1] != ',' &&
line[n + 1] != ';')
{
/* One case where there may be garbage after the right
* bracket is, for example, when declaring a until or
* structure variable using an un-named union or
* structure.
*/
if (prevdnest <= 0 || dnest > 0)
{
/* REVISIT: Generates false alarms on named structures
* that are fields of other structures or unions.
*/
fprintf(stderr,
"Garbage follows right bracket at line %d:%d\n",
lineno, n);
}
}
}
break;
/* Handle logic with parenthese */
case '(':
{
/* Increase the parenthetical nesting level */
pnest++;
/* Check for inappropriate space around parentheses */
if (line[n + 1] == ' ' /* && !bfor */)
{
fprintf(stderr,
"Space follows left parenthesis at line %d:%d\n",
lineno, n);
}
}
break;
case ')':
{
/* Decrease the parenthetical nesting level */
if (pnest < 1)
{
fprintf(stderr, "Unmatched right parentheses at line %d:%d\n",
lineno, n);
pnest = 0;
}
else
{
pnest--;
}
/* Allow ')' as first thing on the line (n == indent)
* Allow "for (xx; xx; )" (bfor == true)
*/
if (n > 0 && n != indent && line[n - 1] == ' ' && !bfor)
{
fprintf(stderr,
"Space precedes right parenthesis at line %d:%d\n",
lineno, n);
}
}
break;
/* Check for inappropriate space around square brackets */
case '[':
{
if (line[n + 1] == ' ')
{
fprintf(stderr,
"Space follows left bracket at line %d:%d\n",
lineno, n);
}
}
break;
case ']':
{
if (n > 0 && line[n - 1] == ' ')
{
fprintf(stderr,
"Space precedes right bracket at line %d:%d\n",
lineno, n);
}
}
break;
/* Semi-colon may terminate a declaration */
case ';':
{
if (!isspace((int)line[n + 1]))
{
fprintf(stderr, "Missing whitespace after semicolon at line %d:%d\n",
lineno, n);
}
/* Semicolon terminates a declaration/definition if there
* was no left curly brace (i.e., dnest is only 1).
*/
if (dnest == 1)
{
dnest = 0;
}
}
break;
/* Semi-colon may terminate a declaration */
case ',':
{
if (!isspace((int)line[n + 1]))
{
fprintf(stderr, "Missing whitespace after comma at line %d:%d\n",
lineno, n);
}
}
break;
case '\r':
{
fprintf(stderr,
"Carriage return detected at line %d:%d\n",
lineno, n);
}
break;
/* Skip over character constants */
case '\'':
{
int endndx = n + 2;
if (line[n + 1] != '\n' && line[n + 1] != '\0')
{
if (line[n + 1] == '\\')
{
for (;
line[endndx] != '\n' &&
line[endndx] != '\0' &&
line[endndx] != '\'';
endndx++);
}
n = endndx + 1;
}
}
break;
/* Check for space around various operators */
case '-':
/* -> */
if (line[n + 1] == '>')
{
n++;
break;
}
case '+':
/* ++, -- */
if (line[n + 1] == line[n])
{
n++;
break;
}
case '&':
/* && */
if (line[n] == '&' && line[n + 1] == line[n])
{
int curr;
int next;
curr = n;
n++;
next = n + 1;
if (line[curr-1] != ' ')
{
fprintf(stderr,
"Operator/assignment must be preceded with whitespace at line %d:%d\n",
lineno, curr);
}
if (line[next] != ' ' && line[next] != '\n')
{
fprintf(stderr,
"Operator/assignment needs whitespace separation at line %d:%d\n",
lineno, curr);
}
break;
}
/* &<variable> OR &(<expression>) */
else if (isalpha((int)line[n + 1]) || line[n + 1] == '_' || line[n + 1] == '(')
{
break;
}
case '/':
{
if (line[n] == '/')
{
if (line[n - 1] == '*')
{
n++;
break;
}
else if (line[n + 1] == '/')
{
fprintf(stderr, "C++ style comment on at %d:%d\n",
lineno, n);
n++;
break;
}
}
}
case '*':
{
/* *\/, ** */
if (line[n] == '*' &&
(line[n + 1] == '/' ||
line[n + 1] == '*'))
{
n++;
break;
}
/* *<variable>, *(<expression>) */
else if (isalpha((int)line[n + 1]) ||
line[n + 1] == '_' ||
line[n + 1] == '(')
{
break;
}
/* (<type> *) */
else if (line[n + 1] == ')')
{
/* REVISIT: This gives false alarms on syntax like *--ptr */
if (line[n - 1] != ' ')
{
fprintf(stderr,
"Operator/assignment must be preceded with whitespace at line %d:%d\n",
lineno, n);
}
break;
}
}
case '%':
{
if (isalnum((int)line[n + 1]))
{
break;
}
}
case '<':
case '>':
case '|':
case '^':
case '=':
{
int curr;
int next;
curr = n;
if (line[curr - 1] != ' ')
{
fprintf(stderr,
"Operator/assignment must be preceded with whitespace at line %d:%d\n",
lineno, curr);
}
next = n + 1;
/* <<, >>, <<=, >>= */
if (line[curr] == '>' || line[curr] == '<')
{
if (line[next] == line[curr])
{
next++;
n++;
}
if (line[next] == '=')
{
next++;
n++;
}
}
else if (line[next] == '=' || line[next] == line[n])
{
next++;
n++;
}
/* REVISIT: This gives false alarms on syntax like *--ptr */
if (line[curr] != '-' && line[next] != ' ' && line[next] != '\n')
{
fprintf(stderr,
"Operator/assignment needs whitespace separation at line %d:%d\n",
lineno, curr);
}
}
break;
case '~':
case '!':
{
int curr;
int next;
curr = n;
next = n + 1;
if (line[next] == '=' || line[next] == line[n])
{
next++;
n++;
if (line[next] != ' ' && line[next] != '\n')
{
fprintf(stderr,
"Operator/assignment needs whitespace separation at line %d:%d\n",
lineno, curr);
}
}
if (line[curr-1] != ' ' && line[curr-1] != '(')
{
fprintf(stderr,
"Operator/assignment must be preceded with whitespace at line %d:%d\n",
lineno, curr);
}
}
break;
default:
break;
}
}
}
/* Loop terminates when NUL or newline character found */
if (line[n] == '\n')
{
/* Check for space at the end of the line */
if (n > 1 && isspace((int)line[n - 1]))
{
fprintf(stderr,
"Dangling whitespace at the end of line %d:%d\n",
lineno, n);
}
/* Check for long lines */
if (n > maxline)
{
fprintf(stderr,
"Long line found at %d:%d\n",
lineno, n);
}
}
/* STEP 4: Check alignment */
/* Within a comment block, we need only check on the alignment of the
* comment.
*/
if ((ncomment > 0 || prevncomment > 0) && !bstring)
{
if (indent == 0 && line[0] != '/')
{
fprintf(stderr, "No indentation line %d:%d\n",
lineno, indent);
}
else if (indent == 1 && line[0] == ' ' && line[1] == '*')
{
/* Good indentation */
}
else if (indent > 0 && line[indent] == '\n')
{
fprintf(stderr, "Whitespace on blank line at line %d:%d\n",
lineno, indent);
}
else if (indent > 0 && indent < 2)
{
if (bnest > 0)
{
fprintf(stderr, "Insufficient indentation line %d:%d\n",
lineno, indent);
}
else
{
fprintf(stderr, "Expected indentation line %d:%d\n",
lineno, indent);
}
}
else if (indent > 0 && !bswitch)
{
if (line[indent] == '/')
{
if ((indent & 3) != 2)
{
fprintf(stderr,
"Bad comment alignment at line %d:%d\n",
lineno, indent);
}
/* REVISIT: This screws up in cases where there is C code,
* followed by a comment that continues on the next line.
*/
else if (line[indent+1] != '*')
{
fprintf(stderr,
"Missing asterisk in comment at line %d:%d\n",
lineno, indent);
}
}
else if (line[indent] == '*')
{
/* REVISIT: Generates false alarms on comments at the end of
* the line if there is nothing preceding (such as the aligned
* comments with a structure field definition). So disabled for
* comments before beginning of function definitions.
*/
if ((indent & 3) != 3 && bfunctions && dnest == 0)
{
fprintf(stderr,
"Bad comment block alignment at line %d:%d\n",
lineno, indent);
}
if (line[indent+1] != ' ' &&
line[indent+1] != '\n' &&
line[indent+1] != '/')
{
fprintf(stderr,
"Invalid character after asterisk in comment block at line %d:%d\n",
lineno, indent);
}
}
/* If this is not the line containing the comment start, then this
* line should begin with '*'
*/
else if (prevncomment > 0)
{
fprintf(stderr, "Missing asterisk in comment block at line %d:%d\n",
lineno, indent);
}
}
}
/* Check for various alignment outside of the comment block */
else if ((ncomment > 0 || prevncomment > 0) && !bstring)
{
if (indent == 0 && strchr("\n#{}", line[0]) == NULL)
{
/* Ignore if we are at global scope */
if (prevbnest > 0)
{
bool blabel = false;
if (isalpha((int)line[indent]))
{
for (i = indent + 1; isalnum((int)line[i]) || line[i] == '_'; i++);
blabel = (line[i] == ':');
}
if (!blabel)
{
fprintf(stderr, "No indentation line %d:%d\n",
lineno, indent);
}
}
}
else if (indent == 1 && line[0] == ' ' && line[1] == '*')
{
/* Good indentation */
}
else if (indent > 0 && line[indent] == '\n')
{
fprintf(stderr, "Whitespace on blank line at line %d:%d\n",
lineno, indent);
}
else if (indent > 0 && indent < 2)
{
fprintf(stderr, "Insufficient indentation line %d:%d\n",
lineno, indent);
}
else if (line[indent] == '{')
{
/* REVISIT: False alarms in data initializers and switch statements */
if ((indent & 3) != 0 && !bswitch && dnest == 0)
{
fprintf(stderr, "Bad left brace alignment at line %d:%d\n",
lineno, indent);
}
}
else if (line[indent] == '}')
{
/* REVISIT: False alarms in data initializers and switch statements */
if ((indent & 3) != 0 && !bswitch && prevdnest == 0)
{
fprintf(stderr, "Bad right brace alignment at line %d:%d\n",
lineno, indent);
}
}
else if (indent > 0)
{
/* REVISIT: Generates false alarms when a statement continues on
* the next line. The bstatm check limits to lines beginnnig with
* C keywords.
* REVISIT: The bstatm check will not detect statements that
* do not begin with a C keyword (such as assignement statements).
* REVISIT: Generates false alarms on comments at the end of
* the line if there is nothing preceding (such as the aligned
* comments with a structure field definition). So disabled for
* comments before beginning of function definitions.
*/
if ((bstatm || /* Begins with C keyword */
(line[indent] == '/' && bfunctions)) && /* Comment in functions */
!bswitch && /* Not in a switch */
dnest == 0) /* Not a data definition */
{
if ((indent & 3) != 2)
{
fprintf(stderr, "Bad alignment at line %d:%d\n",
lineno, indent);
}
}
/* Crazy cases. There should be no small odd alignements
* outside of comment/string. Odd alignments are possible
* on continued lines, but not if they are small.
*/
else if (indent == 1 || indent == 3)
{
fprintf(stderr, "Small odd alignment at line %d:%d\n",
lineno, indent);
}
}
}
}
if (ncomment > 0 || bstring)
{
fprintf(stderr, "In a comment/string at end of file\n");
}
fclose(instream);
return 0;
}