
/* Copyright (C) 1992  AHPCRC, Univeristy of Minnesota
 *
 * 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 2 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 GNU General Public License
 * along with this program in a file named 'Copying'; if not, write to
 * the Free Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139.
 */

/* Author:
 *	Ken Chin-Purcell (ken@ahpcrc.umn.edu)
 *	Army High Performance Computing Research Center (AHPCRC)
 *	Univeristy of Minnesota
 *
 * $Header: /usr/people/ken/gvl/lib/RCS/parse.c,v 2.2 92/10/19 16:55:34 ken Exp $
 *
 * $Log:	parse.c,v $
 * Revision 2.2  92/10/19  16:55:34  ken
 * Trivial change
 * 
 * Revision 1.1  92/10/19  16:37:08  ken
 * Initial revision
 * 
 */
#include <ctype.h>
#include <string.h>
#include <stdio.h>

#include "util.h"
#include "skip.h"
#include "parse.h"


/* InputKind keeps track of special characters
 */
typedef enum _InputKind {
    InputLine, InputChar, InputWhite, 
    InputDoubleQuote, InputSingleQuote,
    InputComment, InputEnd, InputNone
    } InputKind;


static	lineCount = 0;		/* Line count, for reporting errors */


static InputKind GetNextChar(FILE *file, int *c)
{
    /* Put the next character from 'file' into 'c', and return
     * what kind of character it is.
     *
     * A character can be escaped by placing a `\` before it.
     * To continue a line, '\''\n' is used.
     */
    *c = fgetc(file);
    switch (*c) {
      case EOF:
	return InputEnd;
      case '"':
	return InputDoubleQuote;
      case '\'':
	return InputSingleQuote;
      case '\n':
	++lineCount;
	return InputLine;
      case '#':
	return InputComment;
      case '\\':
	*c = fgetc(file);
	if (*c == EOF)
	    return InputEnd;
	if (*c == '\n')
	    ++lineCount;
	/* fall through */
      default:
	if (isspace(*c)  ||  *c == '\r')
	    return InputWhite;
	return InputChar;
    }
}


#define	BUFSIZE	256

static void BuffAddChar(char *buf, int *n, char c)
{
    /* Add c to a running buffer, to build tokens and increment n.
     * The overflow warning is only given once, for sanity sake.
     */
    static int	warning = 0;
    
    if (*n >= BUFSIZE - 1) {
	if (!warning) {
	    Error("PackMan file error: line %d\n", lineCount);
	    Error("    token too large (max = %d characters)\n", BUFSIZE);
	    Error("    token = %s\n", buf);
	    warning = 1;
	}
    } else {
	buf[*n] = c;
	++(*n);
    }
}


static void AddToken(char *buf, int *n, SkipStack tokens)
{
    /* Terminate and add a token to the SkipStack, 
     * reset character count.
     */
    buf[*n] = '\0';
    *n = 0;
    StackAddLast(tokens, NewString(buf, 0));
}


SkipStack ParseOneLine(FILE *file)
{
    /* Parse one "line", which may be contained on several lines
     * if the newline is escaped with '\' at the end of the line.
     * Place each token in an allocated string, and return the list
     * of strings as a SkipStack.
     *
     * Return NULL if at end of the file.
     *
     * Comments and blank lines are skipped over.
     * Comments start with '#' and comment the rest of the line.
     * Items in quotes, either single or double, are considered one token.
     * Quotes can extend over newlines.
     * Otherwise, white space delimits tokens.
     */
    char	buf[BUFSIZE];
    SkipStack	tokens = StackNew();
    int		c, n = 0;
    InputKind	qk, ik = InputNone;
    
    while (ik != InputEnd  &&  StackSize(tokens) == 0) {
	ik = InputNone;
	while (ik != InputLine  &&  ik != InputEnd)
	    switch (ik = GetNextChar(file, &c)) {
	      case InputChar:
		BuffAddChar(buf, &n, c);
		break;
	      case InputWhite:
	      case InputLine:
	      case InputEnd:
		if (n)
		    AddToken(buf, &n, tokens);
		break;
	      case InputSingleQuote:
	      case InputDoubleQuote:
		qk = ik;
		ik = GetNextChar(file, &c);
		while (ik != InputEnd  &&  ik != qk) {
		    BuffAddChar(buf, &n, c);
		    ik = GetNextChar(file, &c);
		}
		AddToken(buf, &n, tokens);
		break;
	      case InputComment:
		do
		    ik = GetNextChar(file, &c);
		while (ik != InputEnd  &&  ik != InputLine);
		break;
	    }
    }
    
    if (ik == InputEnd  &&  StackSize(tokens) == 0) {
	StackFree(tokens, NULL);
	tokens = NULL;
    }
    return tokens;
}


int ParseLineCount(void)
{
    /* Return the number of lines
     * (that's real lines in the file, or number of newlines read.
     *  escaped newlines are counted here).
     * The intent is to give the user a clue as to where in the
     * file an error might be.
     */
    return lineCount;
}


void ParseResetLineCount(void)
{
    /* Reset the line count for a new file.
     */
    lineCount = 0;
}

