/* glptext.c */

/*----------------------------------------------------------------------
-- Copyright (C) 2000, 2001, 2002 Andrew Makhorin <mao@mai2.rcnet.ru>,
--               Department for Applied Informatics, Moscow Aviation
--               Institute, Moscow, Russia. All rights reserved.
--
-- This file is a part of GLPK (GNU Linear Programming Kit).
--
-- GLPK 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 2, or (at your option)
-- any later version.
--
-- GLPK 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 GNU General Public License
-- along with GLPK; see the file COPYING. If not, write to the Free
-- Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
-- 02111-1307, USA.
----------------------------------------------------------------------*/

#include <ctype.h>
#include <errno.h>
#include <stdio.h>
#include <string.h>
#include "glplib.h"
#include "glpstr.h"
#include "glptext.h"

#define error print

/*----------------------------------------------------------------------
-- open_text - open text stream.
--
-- *Synopsis*
--
-- #include "glptext.h"
-- TEXT *open_text(char *fname);
--
-- *Description*
--
-- The open_text routine opens a text stream and connects it with an
-- input text file, whose name is the character string fname.
--
-- *Returns*
--
-- If the operation was successful, the routine returns a pointer to
-- the open text stream. Otherwise the routine returns NULL. */

TEXT *open_text(char *fname)
{     TEXT *text;
      FILE *fp;
      fp = fopen(fname, "r");
      if (fp == NULL)
      {  error("open_text: unable to open `%s' - %s", fname,
            strerror(errno));
         return NULL;
      }
      text = umalloc(sizeof(TEXT));
      text->fp = fp;
      text->file = umalloc(strlen(fname)+1);
      strcpy(text->file, fname);
      text->line = 0;
      text->c = '?';
      text->token = T_EOF;
      text->len = 0;
      text->image[0] = '\0';
      text->t_int = 0;
      text->t_real = 0.0;
      text->err = 0;
      return text;
}

/*----------------------------------------------------------------------
-- scan_token - scan the next token from text stream.
--
-- *Synopsis*
--
-- #include "glptext.h"
-- void scan_token(TEXT *text);
--
-- *Description*
--
-- The scan_token routine scans the next token (syntactic unit) from
-- the text stream, which the parameter text points to, and stores this
-- token to the structure TEXT.
--
-- The calling routine can obtain information about the next token from
-- the following fields:
--
-- text->token    is a code of the current token;
-- text->image    is a character string which literally represents the
--                current token;
-- text->t_int    is a value of the integer constant (only in case when
--                text->token is T_INT);
-- text->t_real   is a value of the integer or the real constant (only
--                in case when text->token is T_INT or T_REAL).
--
-- If some context is invalid, the scan_token routine sends an error
-- message to stderr and corrects the error, so the calling program may
-- think that there are no lexical errors in the text stream. However,
-- after processing the text the calling program should check the field
-- text->err, which is an error count (the scan_token routine increases
-- this count each time when it detects a lexical error). */

/*----------------------------------------------------------------------
-- get_char - read the next character from the text stream.
--
-- This routine reads the next character from the specified text stream
-- and stores it to the TEXT structure. */

static void get_char(TEXT *text)
{     FILE *fp = text->fp;
      text->c = fgetc(fp);
      if (ferror(fp))
      {  error("%s:%d: read error - %s", text->file, text->line,
            strerror(errno));
         text->err++;
         text->c = EOF;
      }
      if (feof(fp)) text->c = EOF;
      /* check for invalid control character */
      if (iscntrl(text->c) && !isspace(text->c))
      {  error("%s:%d: invalid control character 0x%02X", text->file,
            text->line, text->c);
         text->err++;
         text->c = ' ';
      }
      if (text->c == '\n') text->line++;
      return;
}

/*----------------------------------------------------------------------
-- add_char - appends the current character to the current token.
--
-- This routine appends the current character to the current token
-- image and reads the next character. */

static void add_char(TEXT *text)
{     if (text->len == 255)
      {  /* token too long - image overflow */
         switch (text->token)
         {  case T_NAME:
               error("%s:%d: symbolic name `%.8s...' too long",
                  text->file, text->line, text->image);
               break;
            case T_INT:
               error("%s:%d: integer constant `%.8s...' too long",
                  text->file, text->line, text->image);
               break;
            case T_REAL:
               error("%s:%d: real constant `%.8s...' too long",
                  text->file, text->line, text->image);
               break;
            case T_STR:
               error("%s:%d: character string \"%.8s...\" too long",
                  text->file, text->line, text->image);
               break;
            default:
               insist(text->token != text->token);
         }
         text->err++;
         text->len = 8;
         text->image[text->len] = '\0';
      }
      /* append the current character */
      text->image[text->len++] = (char)text->c;
      text->image[text->len] = '\0';
      /* read the next character */
      get_char(text);
      return;
}

/*----------------------------------------------------------------------
-- scan_token - scan the next token from the text stream.
--
-- This routine scans the next token (syntactic unit) and stores its
-- code, image, and converted value (in case of numeric constant) to
-- the TEXT structure. */

void scan_token(TEXT *text)
{     /* read the very first character */
      if (text->line == 0)
      {  text->line = 1;
         get_char(text);
      }
loop: /* scan the next token */
      text->token = -1;
      text->len = 0;
      text->image[0] = '\0';
      text->t_int = 0;
      text->t_real = 0.0;
      /* skip non-significant characters */
      while (isspace(text->c)) get_char(text);
      /* analyze main cases */
      if (text->c == EOF)
      {  /* end of text */
         text->token = T_EOF;
      }
      else if (isalpha(text->c) || text->c == '_')
      {  /* symbolic name (identifier) */
         text->token = T_NAME;
         while (isalnum(text->c) || text->c == '_') add_char(text);
      }
      else if (isdigit(text->c))
      {  /* integer or real constant */
         text->token = T_INT;
         /* scan integer part */
         while (isdigit(text->c)) add_char(text);
         /* scan optional fractional part */
         if (text->c == '.')
         {  text->token = T_REAL;
            add_char(text);
frac:       while (isdigit(text->c)) add_char(text);
         }
         /* scan optional decimal exponent */
         if (text->c == 'e' || text->c == 'E')
         {  text->token = T_REAL;
            add_char(text);
            if (text->c == '+' || text->c == '-') add_char(text);
            if (!isdigit(text->c))
            {  error("%s:%d: real constant `%s' incomplete",
                  text->file, text->line, text->image);
               text->err++;
            }
            while (isdigit(text->c)) add_char(text);
         }
         /* convert to numeric value */
         switch (text->token)
         {  case T_INT:
               if (str2int(text->image, &text->t_int))
               {  error("%s:%d: integer constant `%s' out of range",
                     text->file, text->line, text->image);
                  text->err++;
                  text->t_int = 1;
               }
               text->t_real = (double)text->t_int;
               break;
            case T_REAL:
               if (str2dbl(text->image, &text->t_real))
               {  error("%s:%d: real constant `%s' out of range",
                     text->file, text->line, text->image);
                  text->err++;
                  text->t_real = 1.0;
               }
               break;
            default:
               insist(text->token != text->token);
         }
      }
      else if (text->c == '"')
      {  /* character string (literal) */
         text->token = T_STR;
         /* skip opening quote */
         get_char(text);
         /* scan string body */
         for (;;)
         {  if (text->c == EOF)
            {  error("%s:%d: premature end of file inside character str"
                  "ing", text->file, text->line);
               text->err++;
               break;
            }
            if (text->c == '"') break;
            add_char(text);
         }
         /* skip closing quote */
         if (text->c == '"') get_char(text);
      }
      else if (text->c == '.')
      {  /* either single point, or real constant */
         text->token = T_SPEC;
         add_char(text);
         if (isdigit(text->c))
         {  text->token = T_REAL;
            goto frac;
         }
      }
      else if (text->c == '/')
      {  /* either single slash, or comment sequence */
         text->token = T_SPEC;
         add_char(text);
         if (text->c == '*')
         {  /* skip comment sequence */
            get_char(text);
            for (;;)
            {  if (text->c == EOF)
               {  error("%s:%d: premature end of file inside comment se"
                     "quence", text->file, text->line);
                  text->err++;
                  break;
               }
               if (text->c == '*')
               {  get_char(text);
                  if (text->c == '/')
                  {  get_char(text);
                     break;
                  }
               }
               get_char(text);
            }
            goto loop;
         }
      }
      else
      {  /* special character (delimiter) */
         text->token = T_SPEC;
         add_char(text);
      }
      /* return to the calling program */
      return;
}

/*----------------------------------------------------------------------
-- close_text - close text stream.
--
-- *Synopsis*
--
-- #include "glptext.h"
-- void close_text(TEXT *text);
--
-- *Description*
--
-- The close_text routine closes the text stream, which the parameter
-- text points to, and frees all resources allocated to this object. */

void close_text(TEXT *text)
{     FILE *fp = text->fp;
      fclose(fp);
      ufree(text->file);
      ufree(text);
      return;
}

/* eof */
