/******************************************************************************\
 *                                                                            *
 * Copyright (C) 2008 Jaroslav Hajek <highegg@gmail.com>                      *
 *                                                                            *
 * This program is free software; you can redistribute it and/or modify it    *
 * under the terms of the GNU General Public License as published by the      *
 * Free Software Foundation; either version 3 of the License, or (at your     *
 * option) any later version.                                                 *
 *                                                                            *
 * This program is distributed in the hope that it will be useful, but        *
 * WITHOUT ANY WARRANTY; without even the implied warranty of                 *
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General   *
 * Public License for more details.                                           *
 *                                                                            *
 * You should have received a copy of the General Public License along with   *
 * this program.  If not, see <http://www.gnu.org/licenses/>.                  *
 *                                                                            *
\******************************************************************************/

#include <stdio.h>
#include <string.h>
#include <ctype.h>


/* creates a wrapper for a transition function */
#define WRAP_TRANS(FUN, TFUN) \
int FUN (FILE *stream) \
{ \
  int c, state = 1; \
  do \
    { \
      c = getc (stream); \
      state = TFUN (state, c); \
    } \
  while (c != EOF && state); \
  return (state ? 0 : 1); \
}

/* Allows integers only. 
machine states:
  1: zero (whitespace level)
  2: number
  3: leading sign
*/
int 
fsm_i_trans (int state, int c)
{
  int ret;

  if (!state)
    ret = 0;
  else if (isspace (c))
    {
      if (state < 3)
        ret = 1;
      else
        ret = 0;
    }
  else if (isdigit (c))
    {
      ret = 2;
    }
  else if (c == '+' || c == '-')
    {
      if (state == 1)
        ret = 3;
      else
        ret = 0;
    }
  else if (c == EOF)
    {
      if (state < 3)
        ret = 1;
      else
        ret = 0;
    }
  else
    ret = 0;

  return ret;
}

WRAP_TRANS(parser_i, fsm_i_trans)

/* Allows integers and reals.
machine states:
  1: zero (whitespace level)
  2: leading part of number
  3: after decimal point
  4: exponent part
  5: leading sign
  6: leading decimal point
  7: exponent E
  8: exponent sign
*/

int 
fsm_f_trans (int state, int c)
{
  int ret;

  if (!state)
    ret = 0;
  else if (isspace (c))
    {
      if (state < 5)
        ret = 1;
      else
        ret = 0;
    }
  else if (isdigit (c))
    {
      if (state == 1 || state == 5)
        ret = 2;
      else if (state == 6)
        ret = 3;
      else if (state > 6)
        ret = 4;
      else
        ret = state;
    }
  else if (c == '.')
    {
      if (state == 2)
        ret = 3;
      else if (state == 1)
        ret = 6;
      else 
        ret = 0;
    }
  else if (c == 'e' || c == 'E')
    {
      if (state == 3 || state == 2)
        ret = 7;
      else
        ret = 0;
    }
  else if (c == '+' || c == '-')
    {
      if (state == 1)
        ret = 5;
      else if (state == 7)
        ret = 8;
      else
        ret = 0;
    }
  else if (c == EOF)
    {
      if (state <= 4)
        ret = 1;
      else
        ret = 0;
    }
  else
    ret = 0;

  return ret;
}

WRAP_TRANS(parser_f, fsm_f_trans)

static const char *usage =
"Usage: validnum [OPTION] [FILE]\n"
"Checks if FILE (or standard input) contains only numbers separated by\n"
"whitespaces. If FILE is not supplied or is \"-\", reads standard input.\n"
"If valid, return 0, otherwise return 1. On error, return 2.\n"
"OPTION may specify:\n"
"  -f:          allow integer and real numbers.\n"
"  -i:          allow integers only.\n"
"  --help:      display this help.\n"
;

int 
main(int argc, char **argv)
{
  /* the file we shall read */
  FILE *input = stdin;
  /* pointer to our finite state machine */
  int (*parser)(FILE *) = parser_f;
  int res;

  /* check for OPTION */
  if (argc > 1)
    {
      argc--; argv++;
      if (! strcmp (argv[0], "-f"))
        parser = parser_f;
      else if (! strcmp (argv[0], "-i"))
        parser = parser_i;
      else
        {
          argc++;
          argv--;
        }
    }

  /* check for FILE */
  if (argc > 1)
    {
      argc--; argv++;
      if (! strcmp (argv[0], "--help"))
        {
          puts (usage);
          return 0;
        }
      else if (strcmp (argv[0], "-"))
        {
          input = fopen (argv[0], "r");
          if (! input) 
            {
              fprintf (stderr, "Could not open file: %s\n", argv[0]);
              return 2;
            }
        }
    }

  /* parse the file */

  res = (*parser) (input);
  
  fclose (input);
  return res;

}

