/********************************************************************************
 * tools/nxstyle.c
 *
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.  The
 * ASF licenses this file to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance with the
 * License.  You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the
 * License for the specific language governing permissions and limitations
 * under the License.
 *
 ********************************************************************************/

/********************************************************************************
 * Included Files
 ********************************************************************************/

#include <stdlib.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdint.h>
#include <string.h>
#include <strings.h>
#include <ctype.h>
#include <limits.h>
#include <unistd.h>
#include <libgen.h>

/********************************************************************************
 * Pre-processor Definitions
 ********************************************************************************/

#define NXSTYLE_VERSION "0.01"

#define LINE_SIZE      512
#define RANGE_NUMBER   4096
#define DEFAULT_WIDTH  78

#define FIRST_SECTION  INCLUDED_FILES
#define LAST_SECTION   PUBLIC_FUNCTION_PROTOTYPES

#define FATAL(m, l, o) message(FATAL, (m), (l), (o))
#define FATALFL(m, s)  message(FATAL, (m), -1, -1)
#define WARN(m, l, o)  message(WARN, (m), (l), (o))
#define ERROR(m, l, o) message(ERROR, (m), (l), (o))
#define ERRORFL(m, s)  message(ERROR, (m), -1, -1)
#define INFO(m, l, o)  message(INFO, (m), (l), (o))
#define INFOFL(m, s)   message(INFO, (m), -1, -1)

/********************************************************************************
 * Private types
 ********************************************************************************/

enum class_e
{
  INFO,
  WARN,
  ERROR,
  FATAL
};

const char *class_text[] =
{
  "info",
  "warning",
  "error",
  "fatal"
};

enum file_e
{
  UNKNOWN  = 0x00,
  C_HEADER = 0x01,
  C_SOURCE = 0x02
};

enum section_s
{
  NO_SECTION = 0,
  INCLUDED_FILES,
  PRE_PROCESSOR_DEFINITIONS,
  PUBLIC_TYPES,
  PRIVATE_TYPES,
  PRIVATE_DATA,
  PUBLIC_DATA,
  PRIVATE_FUNCTIONS,
  PRIVATE_FUNCTION_PROTOTYPES,
  INLINE_FUNCTIONS,
  PUBLIC_FUNCTIONS,
  PUBLIC_FUNCTION_PROTOTYPES
};

enum pptype_e
{
  PPLINE_NONE = 0,
  PPLINE_DEFINE,
  PPLINE_IF,
  PPLINE_ELIF,
  PPLINE_ELSE,
  PPLINE_ENDIF,
  PPLINE_OTHER
};

struct file_section_s
{
  const char *name;   /* File section name */
  uint8_t     ftype;  /* File type where section found */
};

/********************************************************************************
 * Private data
 ********************************************************************************/

static enum file_e g_file_type  = UNKNOWN;
static enum section_s g_section = NO_SECTION;
static int g_maxline            = DEFAULT_WIDTH;
static int g_status             = 0;
static int g_verbose            = 2;
static int g_rangenumber        = 0;
static int g_rangestart[RANGE_NUMBER];
static int g_rangecount[RANGE_NUMBER];
static char g_file_name[PATH_MAX];
static bool g_skipmixedcase;

static const struct file_section_s g_section_info[] =
{
  {
    " *\n",                             /* Index: NO_SECTION */
    C_SOURCE | C_HEADER
  },
  {
    " * Included Files\n",              /* Index: INCLUDED_FILES */
    C_SOURCE | C_HEADER
  },
  {
    " * Pre-processor Definitions\n",   /* Index: PRE_PROCESSOR_DEFINITIONS */
    C_SOURCE | C_HEADER
  },
  {
    " * Public Types\n",                /* Index: PUBLIC_TYPES */
    C_HEADER
  },
  {
    " * Private Types\n",               /* Index: PRIVATE_TYPES */
    C_SOURCE
  },
  {
    " * Private Data\n",                /* Index: PRIVATE_DATA */
    C_SOURCE
  },
  {
    " * Public Data\n",                 /* Index: PUBLIC_DATA */
    C_SOURCE | C_HEADER
  },
  {
    " * Private Functions\n",           /* Index: PRIVATE_FUNCTIONS */
    C_SOURCE
  },
  {
    " * Private Function Prototypes\n", /* Index: PRIVATE_FUNCTION_PROTOTYPES */
    C_SOURCE
  },
  {
    " * Inline Functions\n",            /* Index: INLINE_FUNCTIONS */
    C_SOURCE | C_HEADER
  },
  {
    " * Public Functions\n",            /* Index: PUBLIC_FUNCTIONS */
    C_SOURCE
  },
  {
    " * Public Function Prototypes\n",  /* Index: PUBLIC_FUNCTION_PROTOTYPES */
    C_SOURCE | C_HEADER
  }
};

static const char *g_white_prefix[] =
{
  "ASCII_",  /* Ref:  include/nuttx/ascii.h */
  "Elf",     /* Ref:  include/elf.h, include/elf32.h, include/elf64.h */
  "PRId",    /* Ref:  inttypes.h */
  "PRIi",    /* Ref:  inttypes.h */
  "PRIo",    /* Ref:  inttypes.h */
  "PRIu",    /* Ref:  inttypes.h */
  "PRIx",    /* Ref:  inttypes.h */
  "SCNd",    /* Ref:  inttypes.h */
  "SCNi",    /* Ref:  inttypes.h */
  "SCNo",    /* Ref:  inttypes.h */
  "SCNu",    /* Ref:  inttypes.h */
  "SCNx",    /* Ref:  inttypes.h */
  "SYS_",    /* Ref:  include/sys/syscall.h */
  "STUB_",   /* Ref:  syscall/syscall_lookup.h, syscall/sycall_stublookup.c */
  "XK_",     /* Ref:  include/input/X11_keysymdef.h */
  "b8",      /* Ref:  include/fixedmath.h */
  "b16",     /* Ref:  include/fixedmath.h */
  "b32",     /* Ref:  include/fixedmath.h */
  "ub8",     /* Ref:  include/fixedmath.h */
  "ub16",    /* Ref:  include/fixedmath.h */
  "ub32",    /* Ref:  include/fixedmath.h */
  "lua_",    /* Ref:  apps/interpreters/lua/lua-5.x.x/src/lua.h */
  "luaL_",   /* Ref:  apps/interpreters/lua/lua-5.x.x/src/lauxlib.h */

  NULL
};

static const char *g_white_suffix[] =
{
  /* Ref:  include/nuttx/wireless/nrf24l01.h */

  "Mbps",
  "kHz",
  "kbps",
  "us",
  NULL
};

static const char *g_white_content_list[] =
{
  /* Ref:  gnu_unwind_find_exidx.c */

  "__EIT_entry",

  /* Ref:  gnu_unwind_find_exidx.c */

  "__gnu_Unwind_Find_exidx",

  /* Ref:  lib_impure.c */

  "__sFILE_fake",

  /* Ref:  stdlib.h */

  "_Exit",

  /* Ref:  stdatomic.h */

  "_Atomic",

  /* Ref:  https://en.cppreference.com/w/c/keyword/_Thread_local */

  "_Thread_local",

  /* Ref:  unwind-arm-common.h */

  "_Unwind",

  /* Ref:
   * https://pubs.opengroup.org/onlinepubs/9699919799/functions/tempnam.html
   */

  "P_tmpdir",

  /* Ref:
   * https://pubs.opengroup.org/onlinepubs/9699919799/functions/tempnam.html
   */

  "L_tmpnam",

  /* Ref:
   * nuttx/compiler.h
   */

  "_Far",
  "_Erom",

  /* Ref:
   * arch/sim/src/sim/up_wpcap.c
   */

  "Address",
  "Description",
  "FirstUnicastAddress",
  "GetAdaptersAddresses",
  "GetProcAddress",
  "LoadLibrary",
  "lpSockaddr",
  "Next",
  "PhysicalAddressLength",
  "PhysicalAddress",
  "WideCharToMultiByte",

  /* Ref:
   * drivers/segger/note_sysview.c
   */

  "SEGGER_SYSVIEW",
  "TaskID",
  "sName",
  "Prio",
  "StackBase",
  "StackSize",

  /* Ref:
   * drivers/segger/syslog_rtt.c
   */

  "SEGGER_RTT",

  /* Ref:
   * fs/nfs/rpc.h
   * fs/nfs/nfs_proto.h
   * fs/nfs/nfs_mount.h
   * fs/nfs/nfs_vfsops.c
   */

  "CREATE3args",
  "CREATE3resok",
  "LOOKUP3args",
  "LOOKUP3filename",
  "LOOKUP3resok",
  "WRITE3args",
  "WRITE3resok",
  "READ3args",
  "READ3resok",
  "REMOVE3args",
  "REMOVE3resok",
  "RENAME3args",
  "RENAME3resok",
  "MKDIR3args",
  "MKDIR3resok",
  "RMDIR3args",
  "RMDIR3resok",
  "READDIR3args",
  "READDIR3resok",
  "SETATTR3args",
  "SETATTR3resok",
  "FS3args",
  "SIZEOF_rpc_reply_read",
  "SIZEOF_rpc_call_write",
  "SIZEOF_rpc_reply_readdir",
  "SIZEOF_nfsmount",

  /* Ref:
   * mm/kasan/kasan.c
   */

  "__asan_loadN",
  "__asan_storeN",
  "__asan_loadN_noabort",
  "__asan_storeN_noabort",

  /* Ref:
   * tools/jlink-nuttx.c
   */

  "RTOS_Init",
  "RTOS_GetVersion",
  "RTOS_GetSymbols",
  "RTOS_GetNumThreads",
  "RTOS_GetCurrentThreadId",
  "RTOS_GetThreadId",
  "RTOS_GetThreadDisplay",
  "RTOS_GetThreadReg",
  "RTOS_GetThreadRegList",
  "RTOS_GetThreadRegList",
  "RTOS_SetThreadReg",
  "RTOS_SetThreadRegList",
  "RTOS_UpdateThreads",

  /* Ref:
   * sim/posix/sim_x11eventloop.c
   */

  "Display",
  "Button1Mask",
  "Button2Mask",
  "Button3Mask",
  "Button1",
  "Button2",
  "Button3",
  "XEvent",
  "XPending",
  "XNextEvent",
  "KeyPress",
  "KeyRelease",
  "MotionNotify",
  "ButtonPress",
  "ButtonRelease",
  "XLookupKeysym",

  /* Ref:
   * sim/posix/sim_x11framebuffer.c
   */

  "Window",
  "XShmSegmentInfo",
  "XImage",
  "XGCValues",
  "XTextProperty",
  "XSizeHints",
  "XOpenDisplay",
  "XCreateSimpleWindow",
  "DefaultRootWindow",
  "XStringListToTextProperty",
  "XSetWMProperties",
  "XMapWindow",
  "XSelectInput",
  "XAllowEvents",
  "XGrabButton",
  "XCreateGC",
  "XSetErrorHandler",
  "XSync",
  "XShmDetach",
  "XDestroyImage",
  "XUngrabButton",
  "XCloseDisplay",
  "XShmQueryExtension",
  "XShmCreateImage",
  "XShmAttach",
  "DefaultVisual",
  "XCreateImage",
  "XGetWindowAttributes",
  "DefaultColormap",
  "XAllocColor",
  "XShmPutImage",
  "XPutImage",
  "Colormap",
  "DefaultScreen",
  "BlackPixel",
  "PSize",
  "PMinSize",
  "PMaxSize",
  "ButtonPressMask",
  "ButtonReleaseMask",
  "PointerMotionMask",
  "KeyPressMask",
  "KeyReleaseMask",
  "ButtonMotionMask",
  "GrabModeAsync",
  "GCGraphicsExposures",
  "XErrorEvent",
  "AnyModifier",
  "None",
  "Status",
  "DoGreen",
  "DoRed",
  "DoBlue",
  "ZPixmap",
  "readOnly",
  "XWindowAttributes",
  "XColor",
  "AsyncBoth",
  "CurrentTime",
  "XUnmapWindow",

  /* Ref:
   * sim/posix/sim_deviceimage.c
   */

  "inflateInit",
  "inflateEnd",
  "Bytef",

  /* Ref:
   * sim/posix/sim_hostmemory.c
   */

  "CreateFileMapping",
  "MapViewOfFile",
  "CloseHandle",
  "UnmapViewOfFile",

  /* Ref:
   * sim/posix/sim_hostmisc.c
   */

  "CaptureStackBackTrace",

  /* Ref:
   * sim/posix/sim_hosttime.c
   */

  "GetSystemTimeAsFileTime",
  "QueryPerformanceFrequency",
  "QueryPerformanceCounter",
  "CreateWaitableTimer",
  "SetWaitableTimer",
  "WaitForSingleObject",
  "dwHighDateTime",
  "dwLowDateTime",
  "QuadPart",

  /* Ref:
   * sim/posix/sim_hostuart.c
   */

  "GetStdHandle",
  "GetConsoleMode",
  "SetConsoleMode",
  "WriteConsole",
  "ReadConsole",
  "FlushConsoleInputBuffer",
  "GetNumberOfConsoleInputEvents",

  /* Ref:
   * apps/testing/drivertest/drivertest_xxx.c
   */

  "CMUnitTest",

  /* Ref:
   * apps/examples/hello_nim/hello_nim_main.c
   */

  "NimMain",

  /* Ref:
   * sim/posix/sim_rawgadget.c
   */

  "bRequestType",
  "bRequest",
  "wValue",
  "wIndex",
  "wLength",
  "bLength",
  "bDescriptorType",
  "bEndpointAddress",
  "bmAttributes",
  "wMaxPacketSize",
  "bInterval",

  /* Ref:
   * sim/posix/sim_libusb.c
   */

  "bNumConfigurations",
  "bDeviceClass",
  "idVendor",
  "idProduct",

  /* Ref:
   * sim/posix/sim_hostmisc.c
   */

  "_NSGetExecutablePath",

  /* Ref:
   * arch/arm/src/nrf52/sdc/nrf.h
   * arch/arm/src/nrf53/sdc/nrf.h
   */

  "IRQn_Type",

  /* Ref:
   * fs/zipfs/zip_vfs.c
   */

  "unzFile",
  "uLong",
  "unzOpen2_64",
  "unzLocateFile",
  "unzOpenCurrentFile",
  "unzClose",
  "unzReadCurrentFile",
  "unzGetCurrentFileInfo64",
  "unzGoToNextFile",
  "unzGoToFirstFile",
  NULL
};

static const char *g_white_headers[] =
{
  "windows.h",
  NULL
};

static const char *g_white_files[] =
{
  /* Skip assembler file headers
   * Ref:
   * libs/libc/machine/arm/arm-acle-compat.h
   * libs/libc/machine/arm/arm_asm.h
   */

  "arm-acle-compat.h",
  "arm_asm.h",
  NULL
};

/********************************************************************************
 * Private Functions
 ********************************************************************************/

/********************************************************************************
 * Name: show_usage
 *
 * Description:
 *
 ********************************************************************************/

static void show_usage(char *progname, int exitcode, char *what)
{
  fprintf(stderr, "%s version %s\n\n", basename(progname), NXSTYLE_VERSION);
  if (what)
    {
      fprintf(stderr, "%s\n", what);
    }

  fprintf(stderr, "Usage:  %s [-m <excess>] [-v <level>] "
                  "[-r <start,count>] <filename>\n",
          basename(progname));
  fprintf(stderr, "        %s -h this help\n", basename(progname));
  fprintf(stderr, "        %s -v <level> where level is\n",
          basename(progname));
  fprintf(stderr, "                   0 - no output\n");
  fprintf(stderr, "                   1 - PASS/FAIL\n");
  fprintf(stderr, "                   2 - output each line (default)\n");
  exit(exitcode);
}

/********************************************************************************
 * Name: skip
 *
 * Description:
 *
 ********************************************************************************/

static int skip(int lineno)
{
  int i;

  for (i = 0; i < g_rangenumber; i++)
    {
      if (lineno >= g_rangestart[i] && lineno < g_rangestart[i] +
          g_rangecount[i])
        {
          return 0;
        }
    }

  return g_rangenumber != 0;
}

/********************************************************************************
 * Name: message
 *
 * Description:
 *
 ********************************************************************************/

static int message(enum class_e class, const char *text, int lineno, int ndx)
{
  FILE *out = stdout;

  if (skip(lineno))
    {
      return g_status;
    }

  if (class > INFO)
    {
      out = stderr;
      g_status |= 1;
    }

  if (g_verbose == 2)
    {
      if (lineno == -1 && ndx == -1)
        {
          fprintf(out, "%s: %s: %s\n", g_file_name, class_text[class], text);
        }
      else
        {
          fprintf(out, "%s:%d:%d: %s: %s\n", g_file_name, lineno, ndx,
                  class_text[class], text);
        }
    }

  return g_status;
}

/********************************************************************************
 * Name: check_spaces_left
 *
 * Description:
 *
 ********************************************************************************/

static void check_spaces_left(char *line, int lineno, int ndx)
{
  /* Unary operator should generally be preceded by a space but make also
   * follow a left parenthesis at the beginning of a parenthetical list or
   * expression or follow a right parentheses in the case of a cast.
   */

  if (ndx-- > 0 && line[ndx] != ' ' && line[ndx] != '(' && line[ndx] != ')')
    {
       ERROR("Operator/assignment must be preceded with whitespace",
             lineno, ndx);
    }
}

/********************************************************************************
 * Name: check_spaces_leftright
 *
 * Description:
 *
 ********************************************************************************/

static void check_spaces_leftright(char *line, int lineno, int ndx1, int ndx2)
{
  if (ndx1 > 0 && line[ndx1 - 1] != ' ')
    {
       ERROR("Operator/assignment must be preceded with whitespace",
             lineno, ndx1);
    }

  if (line[ndx2 + 1] != '\0' && line[ndx2 + 1] != '\n' && line[ndx2 + 1] != ' ')
    {
       ERROR("Operator/assignment must be followed with whitespace",
             lineno, ndx2);
    }
}

/********************************************************************************
 * Name: check_nospaces_leftright
 *
 * Description:
 *   Check if there are whitespaces on the left of right. If there is, report
 *   an error.
 *
 ********************************************************************************/

static void check_nospaces_leftright(char *line, int lineno, int ndx1, int ndx2)
{
  if (ndx1 > 0 && line[ndx1 - 1] == ' ')
    {
      ERROR("There should be no spaces before the operator/assignment",
            lineno, ndx1);
    }

  if (line[ndx2 + 1] == ' ')
    {
      ERROR("There should be no spaces after the operator/assignment",
            lineno, ndx2);
    }
}

/********************************************************************************
 * Name: check_operand_leftright
 *
 * Description:
 *   Check if the operator is next to an operand. If not, report the error.
 *
 ********************************************************************************/

static void check_operand_leftright(char *line, int lineno, int ndx1, int ndx2)
{
  /* The cases below includes("xx" represents the operator):
   *   " xx " | " xx(end)" | " xx;" | " xx\n" | " xx)" | " xx]"  - (ndx1 > 0)
   *   "(xx " | "(xx(end)" | "(xx;" | "(xx\n" | "(xx)" | "(xx]"  - (ndx1 > 0)
   *   "[xx " | "[xx(end)" | "[xx;" | "[xx\n" | "[xx)" | "[xx]"  - (ndx1 > 0)
   *   "xx "  | "xx(end)"  | "xx;"  | "xx\n"  | "xx)"  | "xx]"   - (ndx1 = 0)
   * In these cases, the operators must be not next any operands, thus errors
   * are reported.
   */

  if (ndx1 > 0 && (line[ndx1 - 1] == ' ' || line[ndx1 - 1] == '(' ||
                   line[ndx1 - 1] == '[') &&
                  (line[ndx2 + 1] == ' ' || line[ndx2 + 1] == '\0' ||
                   line[ndx2 + 1] == ';' || line[ndx2 + 1] == '\n' ||
                   line[ndx2 + 1] == ')' || line[ndx2 + 1] == ']'))
    {
      ERROR("Operator must be next to an operand", lineno, ndx2);
    }
}

/********************************************************************************
 * Name: block_comment_width
 *
 * Description:
 *   Get the width of a block comment
 *
 ********************************************************************************/

static int block_comment_width(char *line)
{
  int b;
  int e;
  int n;

  /* Skip over any leading whitespace on the line */

  for (b = 0; isspace(line[b]); b++)
    {
    }

  /* Skip over any trailing whitespace at the end of the line */

  for (e = strlen(line) - 1; e >= 0 && isspace(line[e]); e--)
    {
    }

  /* Number of characters on the line */

  n = e - b + 1;
  if (n < 4)
    {
      return 0;
    }

  /* The first line of a block comment starts with "[slash]***" and ends with
   * "***"
   */

  if (strncmp(&line[b], "/***", 4) == 0 &&
      strncmp(&line[e - 2], "***", 3) == 0)
    {
      /* Return the the length of the line up to the final '*' */

      return e + 1;
    }

  /* The last line of a block begins with whitespace then "***" and ends
   * with "***[slash]"
   */

  if (strncmp(&line[b], "***", 3) == 0 &&
      strncmp(&line[e - 3], "***/", 4) == 0)
    {
      /* Return the the length of the line up to the final '*' */

      return e;
    }

  /* But there is also a special single line comment that begins with "[slash]* "
   * and ends with "***[slash]"
   */

  if (strncmp(&line[b], "/*", 2) == 0 &&
      strncmp(&line[e - 3], "***/", 4) == 0)
    {
      /* Return the the length of the line up to the final '*' */

      return e;
    }

  /* Return zero if the line is not the first or last line of a block
   * comment.
   */

  return 0;
}

/********************************************************************************
 * Name: get_line_width
 *
 * Description:
 *   Get the maximum line width by examining the width of the block comments.
 *
 ********************************************************************************/

static int get_line_width(FILE *instream)
{
  char line[LINE_SIZE]; /* The current line being examined */
  int max        = 0;
  int min        = INT_MAX;
  int lineno     = 0;
  int lineno_max = 0;
  int lineno_min = 0;
  int len;

  while (fgets(line, LINE_SIZE, instream))
    {
      lineno++;
      len = block_comment_width(line);
      if (len > 0)
        {
          if (len > max)
            {
              max = len;
              lineno_max = lineno;
            }

          if (len < min)
            {
              min = len;
              lineno_min = lineno;
            }
        }
    }

  if (max < min)
    {
      ERRORFL("No block comments found", g_file_name);
      return DEFAULT_WIDTH;
    }
  else if (max != min)
    {
      ERROR("Block comments have different lengths", lineno_max, max);
      ERROR("Block comments have different lengths", lineno_min, min);
      return DEFAULT_WIDTH;
    }

  return min;
}

/********************************************************************************
 * Name:  check_section_header
 *
 * Description:
 *   Check if the current line holds a section header
 *
 ********************************************************************************/

static bool check_section_header(const char *line, int lineno)
{
  int i;

  /* Search g_section_info[] to find a matching section header line */

  for (i = FIRST_SECTION; i <= LAST_SECTION; i++)
    {
      if (strcmp(line, g_section_info[i].name) == 0)
        {
          g_section = (enum section_s)i;

          /* Verify that this section is appropriate for this file type */

          if ((g_file_type & g_section_info[i].ftype) == 0)
            {
              ERROR("Invalid section for this file type", lineno, 3);
            }

          return true;
        }
    }

  return false;
}

/********************************************************************************
 * Name: white_file_list
 *
 * Description:
 *   Return true if the filename string with a white-listed name
 *
 ********************************************************************************/

static bool white_file_list(const char *filename)
{
  const char **pptr;
  const char *str;

  for (pptr = g_white_files;
       (str = *pptr) != NULL;
       pptr++)
    {
      if (strstr(filename, str) != NULL)
        {
          return true;
        }
    }

  return false;
}

/********************************************************************************
 * Name:  white_content_list
 *
 * Description:
 *   Return true if the identifier string begins with a white-listed prefix
 *
 ********************************************************************************/

static bool white_content_list(const char *ident, int lineno)
{
  const char **pptr;
  const char *str;
  size_t len2;
  size_t len;

  for (pptr = g_white_prefix;
       (str = *pptr) != NULL;
       pptr++)
    {
      len = strlen(str);
      if (strncmp(ident, str, len) == 0)
        {
          return true;
        }
    }

  for (pptr = g_white_headers;
       (str = *pptr) != NULL;
       pptr++)
    {
      if (strstr(ident, str) != NULL)
        {
          return true;
        }
    }

  len2 = strlen(ident);
  while (!isalnum(ident[len2 - 1]))
    {
      len2--;
    }

  for (pptr = g_white_suffix;
       (str = *pptr) != NULL;
       pptr++)
    {
      len = strlen(str);
      if (len2 >= len && strncmp(ident + len2 - len, str, len) == 0)
        {
          return true;
        }
    }

  for (pptr = g_white_content_list;
       (str = *pptr) != NULL;
       pptr++)
    {
      len = strlen(str);
      if (strncmp(ident, str, len) == 0 &&
          isalnum(ident[len]) == 0)
        {
          return true;
        }
    }

  return false;
}

/********************************************************************************
 * 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 buffer[100];     /* Localy format error strings */
  char *lptr;           /* Temporary pointer into line[] */
  char *ext;            /* Temporary file extension */
  bool btabs;           /* True: TAB characters found on the line */
  bool bcrs;            /* True: Carriage return 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 terminator */
  bool bexternc;        /* True: Within 'extern "C"' */
  enum pptype_e ppline; /* > 0: The next line the continuation of a
                         * pre-processor command */
  int rhcomment;        /* Indentation of Comment to the right of code
                         * (-1 -> don't check position) */
  int prevrhcmt;        /* Indentation of previous Comment to the right
                         * of code (-1 -> don't check position) */
  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 ppifnest;         /* #if nesting level on this line */
  int inasm;            /* > 0: Within #ifdef __ASSEMBLY__ */
  int comment_lineno;   /* Line on which the last 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 lbrace_lineno;    /* Line number of last left brace */
  int rbrace_lineno;    /* Last line containing a right brace */
  int externc_lineno;   /* Last line where 'extern "C"' declared */
  int linelen;          /* Length of the line */
  int excess;
  int n;
  int i;
  int c;

  excess = 0;
  while ((c = getopt(argc, argv, ":hv:gm:r:")) != -1)
    {
      switch (c)
      {
      case 'm':
        excess = atoi(optarg);
        if (excess < 1)
          {
            show_usage(argv[0], 1, "Bad value for <excess>.");
            excess = 0;
          }

        break;

      case 'v':
        g_verbose = atoi(optarg);
        if (g_verbose < 0 || g_verbose > 2)
          {
            show_usage(argv[0], 1, "Bad value for <level>.");
          }

        break;

      case 'r':
        g_rangestart[g_rangenumber] = atoi(strtok(optarg, ","));
        g_rangecount[g_rangenumber++] = atoi(strtok(NULL, ","));
        break;

      case 'h':
        show_usage(argv[0], 0, NULL);
        break;

      case ':':
        show_usage(argv[0], 1, "Missing argument.");
        break;

      case '?':
        show_usage(argv[0], 1, "Unrecognized option.");
        break;

      default:
        show_usage(argv[0], 0, NULL);
        break;
      }
  }

  if (optind < argc - 1 || argv[optind] == NULL)
    {
      show_usage(argv[0], 1, "No file name given.");
    }

  /* Resolve the absolute path for the input file */

  if (realpath(argv[optind], g_file_name) == NULL)
    {
      FATALFL("Failed to resolve absolute path.", g_file_name);
      return 1;
    }

  /* Are we parsing a header file? */

  ext = strrchr(g_file_name, '.');

  if (ext == 0)
    {
    }
  else if (strcmp(ext, ".h") == 0)
    {
      g_file_type = C_HEADER;
    }
  else if (strcmp(ext, ".c") == 0)
    {
      g_file_type = C_SOURCE;
    }

  if (g_file_type == UNKNOWN)
    {
      return 0;
    }

  if (white_file_list(g_file_name))
    {
      return 0;
    }

  instream = fopen(g_file_name, "r");

  if (!instream)
    {
      FATALFL("Failed to open", g_file_name);
      return 1;
    }

  /* Determine the line width */

  g_maxline = get_line_width(instream) + excess;
  rewind(instream);

  btabs          = false;       /* True: TAB characters found on the line */
  bcrs           = false;       /* True: Carriage return 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 */
  bexternc       = false;       /* True: Within 'extern "C"' */
  ppline         = PPLINE_NONE; /* > 0: The next line the continuation of a
                                 * pre-processor command */
  rhcomment      = 0;           /* Indentation of Comment to the right of code
                                 * (-1 -> don't check position) */
  prevrhcmt      = 0;           /* Indentation of previous Comment to the right
                                 * of code (-1 -> don't check position) */
  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 */
  ppifnest       = 0;           /* #if nesting level on this line */
  inasm          = 0;           /* > 0: Within #ifdef __ASSEMBLY__ */
  comment_lineno = -1;          /* Line on which the last 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 */
  lbrace_lineno  = -1;          /* Line number of last left brace */
  rbrace_lineno  = -1;          /* Last line containing a right brace */
  externc_lineno = -1;          /* Last line where 'extern "C"' declared */

  /* Process each line in the input stream */

  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 */

      /* If we are not in a comment, then this certainly is not a right-hand
       * comment.
       */

      prevrhcmt = rhcomment;
      if (ncomment <= 0)
        {
          rhcomment = 0;
        }

      /* Check for a blank line */

      for (n = 0; line[n] != '\n' && isspace((int)line[n]); n++)
        {
        }

      if (line[n] == '\n')
        {
          if (n > 0)
            {
              ERROR("Blank line contains whitespace", lineno, 1);
            }

          if (lineno == 1)
            {
              ERROR("File begins with a blank line", 1, 1);
            }
          else if (lineno == blank_lineno + 1)
            {
              ERROR("Too many blank lines", lineno, 1);
            }
          else if (lineno == lbrace_lineno + 1)
            {
              ERROR("Blank line follows left brace", lineno, 1);
            }

          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.
               *
               * REVISIT: prevrhcmt is tested to case the preceding line
               * contained comments to the right of the code.  In such cases,
               * the comments are normally aligned and do not follow normal
               * indentation rules.  However, this code will generate a false
               * alarm if the comments are aligned to the right BUT the
               * preceding line has no comment.
               */

              if (line[n] != '}' && line[n] != '#' && prevrhcmt == 0)
                {
                   ERROR("Missing blank line after comment", comment_lineno,
                         1);
                }
            }

          /* 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] != '*'))
            {
               ERROR("Missing file header comment block", lineno, 1);
            }

          if (lineno == 2)
            {
              if (line[n] == '*' && line[n + 1] == '\n')
                {
                  ERROR("Missing relative file path in file header", lineno,
                        n);
                }
              else if (isspace(line[n + 2]))
                {
                  ERROR("Too many whitespaces before relative file path",
                        lineno, n);
                }
              else
                {
                  const char *apps_dir = "apps/";
                  const size_t apps_len = strlen(apps_dir);
                  size_t offset;

#ifdef TOPDIR
                  /* TOPDIR macro contains the absolute path to the "nuttx"
                   * root directory. It should have been defined via Makefile
                   * and it is required to accurately evaluate the relative
                   * path contained in the file header. Otherwise, skip this
                   * verification.
                   */

                  char *basedir = strstr(g_file_name, TOPDIR);
                  if (basedir != NULL)
                    {
                      /* Add 1 to the offset for the slash character */

                      offset = strlen(TOPDIR) + 1;

                      /* Duplicate the line from the beginning of the
                       * relative file path, removing the '\n' at the end of
                       * the string.
                       */

                      char *line_dup = strndup(&line[n + 2],
                                               strlen(&line[n + 2]) - 1);

                      if (strcmp(line_dup, basedir + offset) != 0)
                        {
                          ERROR("Relative file path does not match actual file",
                                lineno, n);
                        }

                      free(line_dup);
                    }
                  else if (strncmp(&line[n + 2], apps_dir, apps_len) != 0)
                    {
                      /* g_file_name neither belongs to "nuttx" repository
                       * nor begins with the root dir of the other
                       * repository (e.g. "apps/")
                       */

                      ERROR("Path relative to repository other than \"nuttx\" "
                            "must begin with the root directory", lineno, n);
                    }
                  else
                    {
#endif

                      offset = 0;

                      if (strncmp(&line[n + 2], apps_dir, apps_len) == 0)
                        {
                          /* Input file belongs to the "apps" repository */

                          /* Calculate the offset to the first directory
                           * after the "apps/" folder.
                           */

                          offset += apps_len;
                        }

                      /* Duplicate the line from the beginning of the
                       * relative file path, removing the '\n' at the end of
                       * the string.
                       */

                      char *line_dup = strndup(&line[n + 2],
                                              strlen(&line[n + 2]) - 1);

                      ssize_t base =
                        strlen(g_file_name) - strlen(&line_dup[offset]);

                      if (base < 0 ||
                          (base != 0 && g_file_name[base - 1] != '/') ||
                          strcmp(&g_file_name[base], &line_dup[offset]) != 0)
                        {
                          ERROR("Relative file path does not match actual file",
                                lineno, n);
                        }

                      free(line_dup);
#ifdef TOPDIR
                    }
#endif
                }
            }

          /* Check for a blank line following a right brace */

          if (bfunctions && lineno == rbrace_lineno + 1)
            {
              /* Check if this line contains a right brace.  A right brace
               * must be followed by 'else', 'while', 'break', a blank line,
               * another right brace, or a pre-processor directive like #endif
               */

              if (dnest == 0 &&
                  strchr(line, '}') == NULL && line[n] != '#' &&
                  strncmp(&line[n], "else", 4) != 0 &&
                  strncmp(&line[n], "while", 5) != 0 &&
                  strncmp(&line[n], "break", 5) != 0)
                {
                   ERROR("Right brace must be followed by a blank line",
                         rbrace_lineno, n + 1);
                }

              /* If the right brace is followed by a pre-processor command
               * like #endif (but not #else or #elif), then set the right
               * brace line number to the line number of the pre-processor
               * command (it then must be followed by a blank line)
               */

              if (line[n] == '#')
                {
                  int ii;

                  for (ii = n + 1; line[ii] != '\0' && isspace(line[ii]); ii++)
                    {
                    }

                  if (strncmp(&line[ii], "else", 4) != 0 &&
                      strncmp(&line[ii], "elif", 4) != 0)
                    {
                      rbrace_lineno = lineno;
                    }
                }
            }
        }

      /* 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)
                  {
                    ERROR("TABs found.  First detected", lineno, n);
                    btabs = true;
                  }

                indent = (indent + 4) & ~3;
              }
              break;

            case '\r':
              {
                if (!bcrs)
                  {
                    ERROR("Carriage returns found.  "
                            "First detected", lineno, n);
                    bcrs = true;
                  }
              }
              break;

            default:
              {
                 snprintf(buffer, sizeof(buffer),
                          "Unexpected white space character %02x found",
                          line[n]);
                 ERROR(buffer, 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 != PPLINE_NONE)
        {
          int len;
          int ii;

          /* Suppress error for comment following conditional compilation */

          noblank_lineno = lineno;

          /* Check pre-processor commands if this is not a continuation
           * line.
           */

          ii = indent + 1;

          if (ppline == PPLINE_NONE)
            {
              /* Skip to the pre-processor command following the '#' */

              while (line[ii] != '\0' && isspace(line[ii]))
                {
                  ii++;
                }

              if (line[ii] != '\0')
                {
                  /* Make sure that pre-processor definitions are all in
                  * the pre-processor definitions section.
                  */

                  ppline = PPLINE_OTHER;

                  if (strncmp(&line[ii], "define", 6) == 0)
                    {
                      ppline = PPLINE_DEFINE;

                      if (g_section != PRE_PROCESSOR_DEFINITIONS)
                        {
                          /* A complication is the header files always have
                           * the idempotence guard definitions before the
                           * "Pre-processor Definitions section".
                           */

                          if (g_section == NO_SECTION &&
                              g_file_type != C_HEADER)
                            {
                              /* Only a warning because there is some usage
                               * of define outside the Pre-processor
                               * Definitions section which is justifiable.
                               * Should be manually checked.
                               */

                              WARN("#define outside of 'Pre-processor "
                                   "Definitions' section",
                                   lineno, ii);
                            }
                        }
                    }

                  /* Make sure that files are included only in the Included
                   * Files section.
                   */

                  else if (strncmp(&line[ii], "include", 7) == 0)
                    {
                      if (g_section != INCLUDED_FILES)
                        {
                          /* Only a warning because there is some usage of
                           * include outside the Included Files section
                           * which may be is justifiable.  Should be
                           * manually checked.
                           */

                          WARN("#include outside of 'Included Files' "
                               "section",
                               lineno, ii);
                        }
                      else if (white_content_list(&line[ii], lineno))
                        {
                          g_skipmixedcase = true;
                        }
                    }
                  else if (strncmp(&line[ii], "if", 2) == 0)
                    {
                      ppifnest++;

                      ppline = PPLINE_IF;
                      ii += 2;
                    }
                  else if (strncmp(&line[ii], "elif", 4) == 0)
                    {
                      if (ppifnest == inasm)
                        {
                          inasm = 0;
                        }

                      ppline = PPLINE_ELIF;
                      ii += 4;
                    }
                  else if (strncmp(&line[ii], "else", 4) == 0)
                    {
                      if (ppifnest == inasm)
                        {
                          inasm = 0;
                        }

                      ppline = PPLINE_ELSE;
                    }
                  else if (strncmp(&line[ii], "endif", 4) == 0)
                    {
                      if (ppifnest == inasm)
                        {
                          inasm = 0;
                        }

                      ppifnest--;

                      ppline = PPLINE_ENDIF;
                    }
               }
            }

          if (ppline == PPLINE_IF || ppline == PPLINE_ELIF)
            {
              int bdef = 0;

              if (strncmp(&line[ii], "def", 3) == 0)
                {
                  bdef = 1;
                  ii += 3;
                }
              else
                {
                  while (line[ii] != '\0' && isspace(line[ii]))
                    {
                      ii++;
                    }

                  if (strncmp(&line[ii], "defined", 7) == 0)
                    {
                      bdef = 1;
                      ii += 7;
                    }
                }

              if (bdef)
                {
                  while (line[ii] != '\0' &&
                      (isspace(line[ii]) || line[ii] == '('))
                    {
                      ii++;
                    }

                  if (strncmp(&line[ii], "__ASSEMBLY__", 12) == 0)
                    {
                      inasm = ppifnest;
                    }
                }
            }

          /* 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--;
            }

          /* Propagate rhcomment over preprocessor lines Issue #120 */

          if (prevrhcmt != 0)
            {
              /* Don't check position */

              rhcomment = -1;
            }

          lptr = strstr(line, "/*");
          if (lptr != NULL)
            {
              n = lptr - &line[0];
              if (line[n + 2] == '\n')
                {
                  ERROR("C comment opening on separate line", lineno, n);
                }
              else if (!isspace((int)line[n + 2]) && line[n + 2] != '*')
                {
                   ERROR("Missing space after opening C comment", lineno, n);
                }

              if (strstr(lptr, "*/") == NULL)
                {
                  /* Increment the count of nested comments */

                  ncomment++;
                }

              if (ppline == PPLINE_DEFINE)
                {
                  rhcomment = n;
                  if (prevrhcmt > 0 && n != prevrhcmt)
                    {
                      rhcomment = prevrhcmt;
                      WARN("Wrong column position of comment right of code",
                          lineno, n);
                    }
                }
              else
                {
                  /* Signal rhcomment, but ignore position */

                  rhcomment = -1;

                  if (ncomment > 0 &&
                      (ppline == PPLINE_IF ||
                       ppline == PPLINE_ELSE ||
                       ppline == PPLINE_ELIF))
                    {
                      /* in #if...  and #el... */

                      ERROR("No multiline comment right of code allowed here",
                          lineno, n);
                    }
                }
            }

          if (line[len] != '\\' || ncomment > 0)
            {
              ppline = PPLINE_NONE;
            }

          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)
            {
              /* If preceding comments were to the right of code, then we can
               * assume that there is a columnar alignment of columns that do
               * no follow the usual alignment.  So the rhcomment flag
               * should propagate.
               */

              rhcomment = prevrhcmt;

              /* 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 &&
                  rhcomment == 0)
                {
                  /* TODO:  This generates a false alarm if preceded
                   * by a label.
                   */

                   ERROR("Missing blank line before comment found", lineno, 1);
                }

              /* 'comment_lineno 'holds the line number of the last closing
               * comment.  It is used only to verify that the comment is
               * followed by a blank line.
               */

              comment_lineno = lineno;
            }
        }

      /* Check for the comment block indicating the beginning of a new file
       * section.
       */

      if (check_section_header(line, lineno))
        {
          if (g_section == PRIVATE_FUNCTIONS || g_section == PUBLIC_FUNCTIONS)
            {
              bfunctions = true;  /* Latched */
            }
        }

      /* 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 (inasm == 0)
        {
          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\n", 7) == 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\n", 6) == 0 ||  /* May be unnamed */
                   strncmp(&line[indent], "unsigned ", 9) == 0 ||
                   strncmp(&line[indent], "void ", 5) == 0 ||
                   strncmp(&line[indent], "volatile ", 9) == 0)
            {
              /* Check if this is extern "C";  We don't typically indent
               * following this.
               */

              if (strncmp(&line[indent], "extern \"C\"", 10) == 0)
                {
                  externc_lineno = lineno;
                }

              /* bfunctions:  True:  Processing private or public functions.
               * bnest:       Brace nesting level on this line
               * dnest:       Data declaration nesting level on this line
               */

              /* 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] == ',')
                        {
                           ERROR("Multiple data definitions", lineno, i + 1);
                          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 ||
    #if 0 /* Part of switch */
                   strncmp(&line[indent], "case ", 5) == 0 ||
    #endif
                   strncmp(&line[indent], "continue ", 9) == 0 ||

    #if 0 /* Part of switch */
                   strncmp(&line[indent], "default ", 8) == 0 ||
    #endif
                   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 ||
    #if 0 /*  Doesn't follow pattern */
                   strncmp(&line[indent], "switch ", 7) == 0 ||
    #endif
                   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)
            {
              ERROR("Missing whitespace after keyword", lineno, n);
              bstatm = true;
            }
          else if (strncmp(&line[indent], "for(", 4) == 0)
            {
              ERROR("Missing whitespace after keyword", lineno, n);
              bfor   = true;
              bstatm = true;
            }
          else if (strncmp(&line[indent], "switch(", 7) == 0)
            {
              ERROR("Missing whitespace after keyword", 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 terminator */

      for (; line[n] != '\n' && line[n] != '\0'; n++)
        {
          /* Report any use of non-standard white space characters */

          if (isspace(line[n]))
            {
              if (line[n] == '\t')
                {
                  if (!btabs)
                    {
                      ERROR("TABs found.  First detected", lineno, n);
                      btabs = true;
                    }
                }
              else if (line[n] == '\r')
                {
                  if (!bcrs)
                    {
                      ERROR("Carriage returns found.  "
                              "First detected", lineno, n);
                      bcrs = true;
                    }
                }
              else if (line[n] != ' ')
                {
                  snprintf(buffer, sizeof(buffer),
                           "Unexpected white space character %02x found",
                           line[n]);
                  ERROR(buffer, lineno, n);
                }
            }

          /* Skip over identifiers */

          if (ncomment == 0 && !bstring && (line[n] == '_' || isalpha(line[n])))
            {
              bool have_upper = false;
              bool have_lower = false;
              int ident_index = n;

              /* Parse over the identifier.  Check if it contains mixed upper-
               * and lower-case characters.
               */

              do
                {
                  have_upper |= isupper(line[n]);

                  /* The coding standard provides for some exceptions of lower
                   * case characters in pre-processor strings:
                   *
                   *   IPv[4|6]    as an IP version number
                   *   ICMPv6      as an ICMP version number
                   *   IGMPv2      as an IGMP version number
                   *   [0-9]p[0-9] as a decimal point
                   *   d[0-9]      as a divisor
                   *   Hz          for frequencies (including KHz, MHz, etc.)
                   */

                   if (!have_lower && islower(line[n]))
                     {
                       switch (line[n])
                       {
                         /* A sequence containing 'v' may occur at the
                          * beginning of the identifier.
                          */

                         case 'v':
                           if (n > 1 &&
                               line[n - 2] == 'I' &&
                               line[n - 1] == 'P' &&
                               (line[n + 1] == '4' ||
                                line[n + 1] == '6'))
                             {
                             }
                           else if (n > 3 &&
                                    line[n - 4] == 'I' &&
                                    line[n - 3] == 'C' &&
                                    line[n - 2] == 'M' &&
                                    line[n - 1] == 'P' &&
                                    line[n + 1] == '6')
                             {
                             }
                           else if (n > 3 &&
                                    line[n - 4] == 'I' &&
                                    line[n - 3] == 'G' &&
                                    line[n - 2] == 'M' &&
                                    line[n - 1] == 'P' &&
                                    line[n + 1] == '2')
                             {
                             }
                           else
                             {
                               have_lower = true;
                             }
                           break;

                         /* Sequences containing 'p', 'd', or 'z' must have
                          * been preceded by upper case characters.
                          */

                         case 'p':
                           if (!have_upper || n < 1 ||
                               !isdigit(line[n - 1]) ||
                               !isdigit(line[n + 1]))
                             {
                               have_lower = true;
                             }
                             break;

                         case 'd':
                           if (!have_upper || !isdigit(line[n + 1]))
                             {
                               have_lower = true;
                             }
                             break;

                         case 'z':
                           if (!have_upper || n < 1 ||
                               line[n - 1] != 'H')
                             {
                               have_lower = true;
                             }
                             break;
                           break;

                         default:
                           have_lower = true;
                           break;
                       }
                     }

                  n++;
                }
              while (line[n] == '_' || isalnum(line[n]));

              /* Check for mixed upper and lower case */

              if (have_upper && have_lower)
                {
                  /* Ignore symbols that begin with white-listed prefixes */

                  if (g_skipmixedcase ||
                      white_content_list(&line[ident_index], lineno))
                    {
                      /* No error */
                    }

                  /* Special case hex constants.  These will look like
                   * identifiers starting with 'x' or 'X' but preceded
                   * with '0'
                   */

                  else if (ident_index < 1 ||
                           (line[ident_index] != 'x' &&
                            line[ident_index] != 'X') ||
                           line[ident_index - 1] != '0')
                    {
                       ERROR("Mixed case identifier found",
                             lineno, ident_index);
                    }
                  else if (have_upper)
                    {
                       ERROR("Upper case hex constant found",
                             lineno, ident_index);
                    }
                }

              /* Check if the identifier is the last thing on the line */

              if (line[n] == '\n' || line[n] == '\0')
                {
                  break;
                }
            }

          /* Handle comments */

          if (line[n] == '/' && !bstring)
            {
              /* Check for start of a C comment */

              if (line[n + 1] == '*')
                {
                  if (line[n + 2] == '\n')
                    {
                      ERROR("C comment opening on separate line", lineno, n);
                    }
                  else if (!isspace((int)line[n + 2]) && line[n + 2] != '*')
                    {
                       ERROR("Missing space after opening C comment", lineno, n);
                    }

                  /* Increment the count of nested comments */

                  ncomment++;

                  /* If there is anything to the left of the left brace, then
                   * this must be a comment to the right of code.
                   * Also if preceding comments were to the right of code, then
                   * we can assume that there is a columnar alignment of columns
                   * that do no follow the usual alignment.  So the rhcomment
                   * flag should propagate.
                   */

                  if (prevrhcmt == 0)
                    {
                      if (n != indent)
                        {
                          rhcomment = n;
                        }
                    }
                  else
                    {
                      rhcomment = n;
                      if (prevrhcmt > 0 && n != prevrhcmt)
                        {
                          rhcomment = prevrhcmt;
                          if (n != indent)
                            {
                              WARN("Wrong column position of "
                                  "comment right of code", lineno, n);
                            }
                          else
                            {
                              ERROR("Wrong column position or missing "
                                  "blank line before comment", lineno, n);
                            }
                        }
                    }

                  n++;
                  continue;
                }

              /* Check for end of a C comment */

              else if (n > 0 && line[n - 1] == '*')
                {
                  if (n < 2)
                    {
                      ERROR("Closing C comment not indented", lineno, n);
                    }
                  else if (!isspace((int)line[n - 2]) && line[n - 2] != '*')
                    {
                       ERROR("Missing space before closing C comment", 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 && rhcomment == 0)
                    {
                       ERROR("Block comment terminator must be on a "
                              "separate line", lineno, n);
                    }

#if 0
                  /* REVISIT: Generates false alarms when portions of an
                   * expression are commented out within the expression.
                   */

                  if (line[n + 1] != '\n')
                    {
                       ERROR("Garbage on line after C comment", 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)
                        {
                          /* 'comment_lineno 'holds the line number of the
                           * last closing comment.  It is used only to
                           * verify that the comment is followed by a blank
                           * line.
                           */

                          comment_lineno = lineno;

                          /* Note that rhcomment must persist to support a
                           * later test for comment alignment.  We will fix
                           * that at the top of the loop when ncomment == 0.
                           */
                        }
                    }
                  else
                    {
                      /* Note that rhcomment must persist to support a later
                       * test for comment alignment.  We will will fix that
                       * at the top of the loop when ncomment == 0.
                       */

                      ncomment = 0;
                      ERROR("Closing without opening comment", lineno, n);
                    }

                  n++;
                  continue;
                }

              /* Check for C++ style comments */

              else if (line[n + 1] == '/')
                {
                  /* Check for URI schemes, e.g. "http://" or "https://" */

                  if ((ncomment == 0) &&
                      (n == 0 || strncmp(&line[n - 1], "://", 3) != 0))
                    {
                      ERROR("C++ style comment", 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 character */

              if (line[n] == '\\')
                {
                  bquote = true;
                  n++;
                }

              /* Check for quoted characters: \" in string */

              if (line[n] == '"' && !bquote)
                {
                  bstring = !bstring;
                }

              bquote = false;
            }

          /* The rest of the line is only examined of we are not in a comment,
           * in a string or in assembly.
           *
           * REVISIT: Should still check for whitespace at the end of the
           * line.
           */

          if (ncomment == 0 && !bstring && inasm == 0)
            {
              switch (line[n])
                {
                /* Handle logic nested with curly braces */

                case '{':
                  {
                    if (n > indent)
                      {
                        /* REVISIT: dnest is always > 0 here if bfunctions ==
                         * false.
                         */

                        if (dnest == 0 || !bfunctions || lineno == rbrace_lineno)
                          {
                             ERROR("Left bracket not on separate line", lineno,
                                   n);
                          }
                      }
                    else if (line[n + 1] != '\n')
                      {
                        if (dnest == 0)
                          {
                             ERROR("Garbage follows left bracket", lineno, n);
                          }
                      }

                    bnest++;
                    if (dnest > 0)
                      {
                        dnest++;
                      }

                    /* Check if we are within 'extern "C"', we don't
                     * normally indent in that case because the 'extern "C"'
                     * is conditioned on __cplusplus.
                     */

                    if (lineno == externc_lineno ||
                        lineno - 1 == externc_lineno)
                      {
                        bexternc = true;
                      }

                    /* Suppress error for comment following a left brace */

                    noblank_lineno = lineno;
                    lbrace_lineno  = lineno;
                  }
                  break;

                case '}':
                  {
                   /* Decrement the brace nesting level */

                   if (bnest < 1)
                     {
                       ERROR("Unmatched right brace", lineno, n);
                     }
                   else
                     {
                       bnest--;
                       if (bnest < 1)
                         {
                           bnest = 0;
                           bswitch = false;
                         }
                     }

                    /* Decrement the declaration nesting level */

                    if (dnest < 3)
                      {
                        dnest = 0;
                        bexternc = false;
                      }
                    else
                      {
                        dnest--;
                      }

                    /* The right brace should be on a separate line */

                    if (n > indent)
                      {
                        if (dnest == 0)
                          {
                             ERROR("Right bracket not on separate line",
                                   lineno, n);
                          }
                      }

                    /* Check for garbage following the left brace */

                    if (line[n + 1] != '\n' &&
                        line[n + 1] != ',' &&
                        line[n + 1] != ';')
                      {
                        int sndx = n + 1;
                        bool whitespace = false;

                        /* Skip over spaces */

                        while (line[sndx] == ' ')
                          {
                            sndx++;
                          }

                        /* One possibility is that the right bracket is
                         * followed by an identifier then a semi-colon.
                         * Comma is possible to but would be a case of
                         * multiple declaration of multiple instances.
                         */

                        if (line[sndx] == '_' || isalpha(line[sndx]))
                          {
                            int endx = sndx;

                            /* Skip to the end of the identifier.  Checking
                             * for mixed case identifiers will be done
                             * elsewhere.
                             */

                            while (line[endx] == '_' ||
                                   isalnum(line[endx]))
                              {
                                endx++;
                              }

                            /* Skip over spaces */

                            while (line[endx] == ' ')
                              {
                                whitespace = true;
                                endx++;
                              }

                            /* Handle according to what comes after the
                             * identifier.
                             */

                            if (strncmp(&line[sndx], "while", 5) == 0)
                              {
                                 ERROR("'while' must be on a separate line",
                                        lineno, sndx);
                              }
                            else if (line[endx] == ',')
                              {
                                 ERROR("Multiple data definitions on line",
                                        lineno, endx);
                              }
                            else if (line[endx] == ';')
                              {
                                if (whitespace)
                                  {
                                     ERROR("Space precedes semi-colon",
                                           lineno, endx);
                                  }
                              }
                            else if (line[endx] == '=')
                              {
                                /* There's a struct initialization following */

                                check_spaces_leftright(line, lineno, endx, endx);
                                dnest = 1;
                              }
                            else
                              {
                                 ERROR("Garbage follows right bracket",
                                       lineno, n);
                              }
                          }
                        else
                          {
                             ERROR("Garbage follows right bracket", lineno, n);
                          }
                      }

                    /* The right brace should not be preceded with a a blank
                     * line.
                     */

                    if (lineno == blank_lineno + 1)
                      {
                         ERROR("Blank line precedes right brace at line",
                                lineno, 1);
                      }

                    rbrace_lineno  = lineno;
                  }
                  break;

                /* Handle logic with parentheses */

                case '(':
                  {
                    /* Increase the parenthetical nesting level */

                    pnest++;

                   /* Check for inappropriate space around parentheses */

                    if (line[n + 1] == ' ')  /* && !bfor */
                      {
                         ERROR("Space follows left parenthesis", lineno, n);
                      }
                  }
                  break;

                case ')':
                  {
                    /* Decrease the parenthetical nesting level */

                    if (pnest < 1)
                     {
                       ERROR("Unmatched right parentheses", 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)
                      {
                         ERROR("Space precedes right parenthesis", lineno, n);
                      }
                  }
                  break;

                /* Check for inappropriate space around square brackets */

                case '[':
                  {
                    if (line[n + 1] == ' ')
                      {
                         ERROR("Space follows left bracket", lineno, n);
                      }
                  }
                  break;

                case ']':
                  {
                    if (n > 0 && line[n - 1] == ' ')
                      {
                         ERROR("Space precedes right bracket", lineno, n);
                      }
                  }
                  break;

                /* Semi-colon may terminate a declaration */

                case ';':
                  {
                    if (!isspace((int)line[n + 1]))
                      {
                        ERROR("Missing whitespace after semicolon", 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]))
                      {
                        ERROR("Missing whitespace after comma", 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;
                      }
                  }
                  break;

                /* Check for space around various operators */

                case '-':

                  /* -> */

                  if (line[n + 1] == '>')
                    {
                      /* -> must have no whitespaces on its left or right */

                      check_nospaces_leftright(line, lineno, n, n + 1);
                      n++;
                    }

                  /* -- */

                  else if (line[n + 1] == '-')
                    {
                      /* "--" should be next to its operand. If there are
                       * whitespaces or non-operand characters on both left
                       * and right (e.g. "a -- ", "a[i --]", "(-- i)"),
                       * there's an error.
                       */

                      check_operand_leftright(line, lineno, n, n + 1);
                      n++;
                    }

                  /* -= */

                  else if (line[n + 1] == '=')
                    {
                      check_spaces_leftright(line, lineno, n, n + 1);
                      n++;
                    }

                  /* Scientific notation with a negative exponent (eg. 10e-10)
                   * REVISIT: This fails for cases where the variable name
                   *          ends with 'e' preceded by a digit:
                   *          a = abc1e-10;
                   *          a = ABC1E-10;
                   */

                  else if ((line[n - 1] == 'e' || line[n - 1] == 'E') &&
                           isdigit(line[n + 1]) && isdigit(line[n - 2]))
                    {
                      n++;
                    }
                  else
                    {
                      /* '-' may function as a unary operator and snuggle
                       * on the left.
                       */

                      check_spaces_left(line, lineno, n);
                    }

                  break;

                case '+':

                  /* ++ */

                  if (line[n + 1] == '+')
                    {
                      /* "++" should be next to its operand. If there are
                       * whitespaces or non-operand characters on both left
                       * and right (e.g. "a ++ ", "a[i ++]", "(++ i)"),
                       * there's an error.
                       */

                      check_operand_leftright(line, lineno, n, n + 1);
                      n++;
                    }

                  /* += */

                  else if (line[n + 1] == '=')
                    {
                      check_spaces_leftright(line, lineno, n, n + 1);
                      n++;
                    }
                  else
                    {
                      /* '+' may function as a unary operator and snuggle
                       * on the left.
                       */

                      check_spaces_left(line, lineno, n);
                    }

                  break;

                case '&':

                  /* &<variable> OR &(<expression>) */

                  if (isalpha((int)line[n + 1]) || line[n + 1] == '_' ||
                      line[n + 1] == '(')
                    {
                    }

                  /* &&, &= */

                  else if (line[n + 1] == '=' || line[n + 1] == '&')
                    {
                      check_spaces_leftright(line, lineno, n, n + 1);
                      n++;
                    }
                  else
                    {
                      check_spaces_leftright(line, lineno, n, n);
                    }

                  break;

                case '/':

                  /* C comment terminator */

                  if (line[n - 1] == '*')
                    {
                      n++;
                    }

                    /* C++-style comment */

                  else if (line[n + 1] == '/')
                    {
                      /* Check for "http://" or "https://" */

                      if ((n < 5 || strncmp(&line[n - 5], "http://", 7) != 0) &&
                          (n < 6 || strncmp(&line[n - 6], "https://", 8) != 0))
                        {
                          ERROR("C++ style comment on at %d:%d\n",
                                lineno, n);
                        }

                      n++;
                    }

                  /* /= */

                  else if (line[n + 1] == '=')
                    {
                      check_spaces_leftright(line, lineno, n, n + 1);
                      n++;
                    }

                  /* Division operator */

                  else
                    {
                      check_spaces_leftright(line, 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] != ' ' && line[n - 1] != '(')
                        {
                           ERROR("Operator/assignment must be preceded "
                                  "with whitespace", lineno, n);
                        }

                      break;
                    }

                  /* *= */

                  else if (line[n + 1] == '=')
                    {
                      check_spaces_leftright(line, lineno, n, n + 1);
                      n++;
                    }
                  else
                    {
                      /* A single '*' may be an binary operator, but
                       * it could also be a unary operator when used to deference
                       * a pointer.
                       */

                      check_spaces_left(line, lineno, n);
                    }

                  break;

                case '%':

                  /* %= */

                  if (line[n + 1] == '=')
                    {
                      check_spaces_leftright(line, lineno, n, n + 1);
                      n++;
                    }
                  else
                    {
                      check_spaces_leftright(line, lineno, n, n);
                    }

                  break;

                case '<':

                  /* <=, <<, <<= */

                  if (line[n + 1] == '=')
                    {
                      check_spaces_leftright(line, lineno, n, n + 1);
                      n++;
                    }
                  else if (line[n + 1] == '<')
                    {
                      if (line[n + 2] == '=')
                        {
                          check_spaces_leftright(line, lineno, n, n + 2);
                          n += 2;
                        }
                      else
                        {
                          check_spaces_leftright(line, lineno, n, n + 1);
                          n++;
                        }
                    }
                  else
                    {
                      check_spaces_leftright(line, lineno, n, n);
                    }

                  break;

                case '>':

                  /* >=, >>, >>= */

                  if (line[n + 1] == '=')
                    {
                      check_spaces_leftright(line, lineno, n, n + 1);
                      n++;
                    }
                  else if (line[n + 1] == '>')
                    {
                      if (line[n + 2] == '=')
                        {
                          check_spaces_leftright(line, lineno, n, n + 2);
                          n += 2;
                        }
                      else
                        {
                          check_spaces_leftright(line, lineno, n, n + 1);
                          n++;
                        }
                    }
                  else
                    {
                      check_spaces_leftright(line, lineno, n, n);
                    }

                  break;

                case '|':

                  /* |=, || */

                  if (line[n + 1] == '=')
                    {
                      check_spaces_leftright(line, lineno, n, n + 1);
                      n++;
                    }
                  else if (line[n + 1] == '|')
                    {
                      check_spaces_leftright(line, lineno, n, n + 1);
                      n++;
                    }
                  else
                    {
                      check_spaces_leftright(line, lineno, n, n);
                    }

                  break;

                case '^':

                  /* ^= */

                  if (line[n + 1] == '=')
                    {
                      check_spaces_leftright(line, lineno, n, n + 1);
                      n++;
                    }
                  else
                    {
                      check_spaces_leftright(line, lineno, n, n);
                    }

                  break;

                case '=':

                  /* == */

                  if (line[n + 1] == '=')
                    {
                      check_spaces_leftright(line, lineno, n, n + 1);
                      n++;
                    }
                  else
                    {
                      check_spaces_leftright(line, lineno, n, n);
                    }

                  break;

                case '~':
                  check_spaces_left(line, lineno, n);
                  break;

                case '!':

                  /* != */

                  if (line[n + 1] == '=')
                    {
                      check_spaces_leftright(line, lineno, n, n + 1);
                      n++;
                    }

                  /* !! */

                  else if (line[n + 1] == '!')
                    {
                      check_spaces_left(line, lineno, n);
                      n++;
                    }
                  else
                    {
                      check_spaces_left(line, lineno, n);
                    }

                  break;

                default:
                  break;
                }
            }
        }

      /* Loop terminates when NUL or newline character found */

      if (line[n] == '\n' || line[n] == '\0')
        {
          /* If the parse terminated on the NULL, then back up to the last
           * character (which should be the newline).
           */

          int m = n;
          if (line[m] == '\0' && m > 0)
            {
              m--;
            }

          /* Check for space at the end of the line.  Except for carriage
           * returns which we have already reported (one time) above.
           */

          if (m > 1 && isspace((int)line[m - 1]) &&
              line[m - 1] != '\n' && line[m - 1] != '\r')
            {
               ERROR("Dangling whitespace at the end of line", lineno, m);
            }

          /* The line width is determined by the location of the final
           * asterisk in block comments.  The closing line of the block
           * comment will exceed that by one one character, the '/'
           * following the final asterisk.
           */

          else if (m > g_maxline)
            {
              bool bslash;
              int a;

              for (bslash = false, a = m;
                   a > 2 && strchr("\n\r/", line[a]) != NULL;
                   a--)
                {
                  if (line[a] == '/')
                    {
                      bslash = true;
                    }
                }

              if (bslash && line[a] == '*')
                {
                  m = a + 1;
                }
            }

          /* Check for long lines
           *
           * REVISIT:  Long line checks suppressed on right hand comments
           * for now.  This just prevents a large number of difficult-to-
           * fix complaints that we would have otherwise.
           */

          if (m > g_maxline && !rhcomment)
            {
              ERROR("Long line found", lineno, m);
            }
        }

      /* STEP 4: Check alignment */

      /* Within a comment block, we need only check on the alignment of the
       * comment.
       */

      if ((ncomment > 0 || prevncomment > 0) && !bstring)
        {
          /* Nothing should begin in comment zero */

          if (indent == 0 && line[0] != '/' && !bexternc)
            {
              /* NOTE:  if this line contains a comment to the right of the
               * code, then ncomment will be misleading because it was
               * already incremented above.
               */

              if (ncomment > 1 || rhcomment == 0)
                {
                  ERROR("No indentation line", lineno, indent);
                }
            }
          else if (indent == 1 && line[0] == ' ' && line[1] == '*')
            {
              /* Good indentation */
            }
          else if (indent > 0 && line[indent] == '\n')
            {
              ERROR("Whitespace on blank line", lineno, indent);
            }
          else if (indent > 0 && indent < 2)
            {
              if (bnest > 0)
                {
                  ERROR("Insufficient indentation", lineno, indent);
                }
              else
                {
                  ERROR("Expected indentation line", lineno, indent);
                }
            }
          else if (indent > 0 && !bswitch)
            {
              if (line[indent] == '/')
                {
                  /* Comments should like at offsets 2, 6, 10, ...
                   * This rule is not followed, however, if the comments are
                   * aligned to the right of the code.
                   */

                  if ((indent & 3) != 2 && rhcomment == 0)
                    {
                       ERROR("Bad comment alignment", 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] != '*')
                    {
                       ERROR("Missing asterisk in comment", 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.
                   *
                   * Suppress this error if this is a comment to the right of
                   * code.
                   * Those may be unaligned.
                   */

                  if ((indent & 3) != 3 && bfunctions && dnest == 0 &&
                      rhcomment == 0)
                    {
                       ERROR("Bad comment block alignment", lineno, indent);
                    }

                  if (line[indent + 1] != ' ' &&
                      line[indent + 1] != '*' &&
                      line[indent + 1] != '\n' &&
                      line[indent + 1] != '/')
                    {
                       ERROR("Invalid character after asterisk "
                             "in comment block", lineno, indent);
                    }
                }

              /* If this is not the line containing the comment start, then this
               * line should begin with '*'
               */

              else if (prevncomment > 0)
                {
                  ERROR("Missing asterisk in comment block", 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 && !bexternc)
                    {
                      ERROR("No indentation line", lineno, indent);
                    }
                }
            }
          else if (indent == 1 && line[0] == ' ' && line[1] == '*')
            {
              /* Good indentation */
            }
          else if (indent > 0 && line[indent] == '\n')
            {
              ERROR("Whitespace on blank line", lineno, indent);
            }
          else if (indent > 0 && indent < 2)
            {
              ERROR("Insufficient indentation line", lineno, indent);
            }
          else if (line[indent] == '{')
            {
              /* Check for left brace in first column, but preceded by a
               * blank line.  Should never happen (but could happen with
               * internal compound statements).
               */

              if (indent == 0 && lineno == blank_lineno + 1)
                {
                  ERROR("Blank line before opening left brace", lineno, indent);
                }

              /* REVISIT:  Possible false alarms in compound statements
               * without a preceding conditional.  That usage often violates
               * the coding standard.
               */

              else if (!bfunctions && (indent & 1) != 0)
                {
                  ERROR("Bad left brace alignment", lineno, indent);
                }
              else if ((indent & 3) != 0 && !bswitch && dnest == 0)
                {
                  ERROR("Bad left brace alignment", lineno, indent);
                }
            }
          else if (line[indent] == '}')
            {
              /* REVISIT:  Possible false alarms in compound statements
               * without a preceding conditional.  That usage often violates
               * the coding standard.
               */

              if (!bfunctions && (indent & 1) != 0)
                {
                  ERROR("Bad left brace alignment", lineno, indent);
                }
              else if ((indent & 3) != 0 && !bswitch && prevdnest == 0)
                {
                  ERROR("Bad right brace alignment", lineno, indent);
                }
            }
          else if (indent > 0)
            {
              /* REVISIT: Generates false alarms when a statement continues on
               * the next line.  The bstatm check limits to lines beginning
               * with C keywords.
               * REVISIT:  The bstatm check will not detect statements that
               * do not begin with a C keyword (such as assignment 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 &&
                  line[indent + 1] == '*')) &&            /* Comment in functions */
                  !bswitch &&                             /* Not in a switch */
                  dnest == 0)                             /* Not a data definition */
                {
                  if ((indent & 3) != 2)
                    {
                      ERROR("Bad alignment", lineno, indent);
                    }
                }

              /* Crazy cases.  There should be no small odd alignments
               * outside of comment/string.  Odd alignments are possible
               * on continued lines, but not if they are small.
               */

              else if (indent == 1 || indent == 3)
                {
                  ERROR("Small odd alignment", lineno, indent);
                }
            }
        }
    }

  if (!bfunctions && g_file_type == C_SOURCE)
    {
      ERROR("\"Private/Public Functions\" not found!"
            " File will not be checked", lineno, 1);
    }

  if (ncomment > 0 || bstring)
    {
      ERROR("Comment or string found at end of file", lineno, 1);
    }

  fclose(instream);
  if (g_verbose == 1)
    {
      fprintf(stderr, "%s: %s nxstyle check\n", g_file_name,
              g_status == 0 ? "PASSED" : "FAILED");
    }

  return g_status;
}