/*
 * lispread.c
 *
 * Herein lies a function to read strings printed by lispDisplay.
 * Additionally, there is a lisp token parser, and a lisp 
 * 
 * the functions are lispRead, lsptok, and LispToken.
 *
 * Bugs:  (nil) won't work.
 *
 * Identification:
 *    $Header: /private/postgres/src/lib/C/RCS/lispread.c,v 1.16 1991/11/19 03:08:25 mer Exp $
 */

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

#include "tmp/postgres.h"

RcsId("$Header: /private/postgres/src/lib/C/RCS/lispread.c,v 1.16 1991/11/19 03:08:25 mer Exp $");

#include "parser/atoms.h"
#include "rules/params.h"
#include "utils/palloc.h"
#include "utils/log.h"

#include "nodes/nodes.h"
#include "nodes/pg_lisp.h"
#include "nodes/primnodes.h"
#include "nodes/primnodes.a.h"

#include "lib/equalfuncs.h"
#include "nodes/pg_lisp.h"
#include "utils/lsyscache.h"
double atof();

#define RIGHT_PAREN (1000000 + 1)
#define LEFT_PAREN  (1000000 + 2)
#define PLAN_SYM    (1000000 + 3)

/*----------------------------------------------------------
 *
 * Param Node handling.
 *
 * makeParamList is a global variable. If it is set to 'true' then
 * every time a Param node is generated, we add an entry to
 * paramList (another global variable).
 * maxParamListEntries is the current maximum number of
 * entries the array can handle.
 */

static bool makeParamList;
static ParamListInfo paramList;
static int maxParamListEntries;

static
void
addToParamList();

/*-------------------------------------------------------------
 * LispToken returns the type of the lisp token contained in token.
 * It returns one of
 * 
 * PGLISP_ATOM
 * PGLISP_DTPR
 * PGLISP_FLOAT
 * PGLISP_INT
 * PGLISP_STR
 * PGLISP_VECI
 *
 * and some of its own:
 *
 * RIGHT_PAREN
 * LEFT_PAREN
 * PLAN_SYM
 * DOTPLAN_SYM
 * 
 * LispToken will check only the
 * first character for many lisp objects, such as strings or vectors.
 *
 * Does no error checking as it assumes the file is electronically
 * generated.
 */

NodeTag lispTokenType(token, length)

char *token;
int length;

{
	NodeTag retval;
	int sign;

	/*
	 * Check if the token is a number (decimal or integer,
	 * positive or negative
	 */
	if (isdigit(*token) ||
	   (length>=2 && *token=='-' && isdigit(*(token+1)) ))
	{
		/*
		 * skip the optional '-' (i.e. negative number)
		 */
		if (*token == '-') {
		    token++;
		}

		/*
		 * See if there is a decimal point
		 */

		for (; length && *token != '.'; token++, length--);
		
		/*
		 * if there isn't, token's an int, otherwise it's a float.
		 */

		retval = (*token != '.') ? PGLISP_INT : PGLISP_FLOAT;
	}
	else if (isalpha(*token))
		retval = PGLISP_ATOM;
	else if (*token == '(')
		retval = LEFT_PAREN;
	else if (*token == ')')
		retval = RIGHT_PAREN;
	else if (*token == '\"')
		retval = PGLISP_STR;
	else if (*token == '.' && length == 1)
		retval = PGLISP_DTPR;
	else if (*token == '#' && token[1] == 'S' && length == 2)
		retval = PLAN_SYM;
	else if (*token == '#' 
              && token[1] == '<'
              && isdigit(token[2])
	      && length < 2)
		retval = PGLISP_VECI;
	return(retval);
}

/*
 * This guy does all the reading.
 *
 * Secrets:  He assumes that lsptok already has the string (see below).
 * Any callers should set read_car_only to true.
 */

LispValue
lispRead(read_car_only)

bool read_car_only;

{
	char *token, *lsptok();
	NodeTag type, lispTokenType();
	LispValue this_value, return_value, parsePlanString();
	int tok_len;
	int i, nbytes;
	char tmp;
	bool make_dotted_pair_cell = false;

	token = lsptok(NULL, &tok_len);

	if (token == NULL) return(NULL);

	type = lispTokenType(token, tok_len);

	
	switch(type)
	{
		case PLAN_SYM:
			token = lsptok(NULL, &tok_len);
			if (token[0] != '(') return(NULL);
			this_value = parsePlanString(NULL);
			token = lsptok(NULL, &tok_len);
			if (token[0] != ')') return(NULL);
			if (makeParamList) {
			    if (IsA(this_value,Param)) {
				addToParamList(this_value);
			    }
			}
			if (!read_car_only)
				make_dotted_pair_cell = true;
			else
				make_dotted_pair_cell = false;
			break;
		case LEFT_PAREN:
			if (!read_car_only)
			{
				this_value = lispDottedPair();
				this_value->val.car = lispRead(false);
				this_value->cdr = lispRead(false);
				if (this_value->cdr == (LispValue) -1)
				{
					this_value->cdr = CAR(lispRead(false));
				}
			}
			else
			{
				this_value = lispRead(false);
			}
			break;
		case RIGHT_PAREN:
			this_value = LispNil;
			break;
		case PGLISP_ATOM:
			if (!strncmp(token, "nil", 3))
			{
				this_value = LispNil;
				/*
				 * It might be "nil" but it is an atom!
				 */
				if (read_car_only) {
				    make_dotted_pair_cell = false;
				} else {
				    make_dotted_pair_cell = true;
				}
			}
			else
			{

				tmp = token[tok_len];
				token[tok_len] = '\0';
				this_value = lispAtom(token);
				token[tok_len] = tmp;
				make_dotted_pair_cell = true;
			}
			break;
		case PGLISP_FLOAT:
			tmp = token[tok_len];
			token[tok_len] = '\0';
			this_value = lispFloat(atof(token));
			token[tok_len] = tmp;
			make_dotted_pair_cell = true;
			break;
		case PGLISP_INT:
			tmp = token[tok_len];
			token[tok_len] = '\0';
			this_value = lispInteger(atoi(token));
			token[tok_len] = tmp;
			make_dotted_pair_cell = true;
			break;
		case PGLISP_STR:
			tmp = token[tok_len - 1];
			token[tok_len - 1] = '\0';
			token++;
			this_value = lispString(token);
			token[tok_len - 2] = tmp;
			make_dotted_pair_cell = true;
			break;
		case PGLISP_VECI:
			sscanf(token, "#<%d:", &nbytes);
			this_value = lispVectori(nbytes);
			for (i = 0; i < nbytes; i++)
			{
				token = lsptok(NULL, tok_len);
				this_value->val.veci->data[i] = atoi(token);
			}
			token = lsptok(NULL, tok_len); /* get > */
			make_dotted_pair_cell = true;
			break;
		case PGLISP_DTPR:
			this_value = (LispValue) -1;
			break;
		default:
			elog(WARN, "Bad type (lispRead)");
			break;
	}
	if (make_dotted_pair_cell)
	{
		return_value = lispDottedPair();
		return_value->val.car = this_value;
		if (!read_car_only)
		{
			return_value->cdr = lispRead(false);
			if (return_value->cdr == (LispValue) -1)
			{
				return_value->cdr = lispRead(false);
			}
		}
		else
		{
			return_value->cdr = LispNil;
		}
	}
	else
	{
		return_value = this_value;
	}
	return(return_value);
} /* lispRead */

/*
 * Works kinda like strtok, except it doesn't put nulls into string.
 * 
 * Returns the length in length instead.  The string can be set without
 * returning a token by calling lsptok with length == NULL.
 *
 */

char *lsptok(string, length)

char *string;
int  *length;

{
	static char *local_str;
	char *ret_string;
	

	if (string != NULL) 
	{
		local_str = string;
		if (length == NULL) {
		    return(NULL);
		}
	}


	for (; *local_str == ' '
	    || *local_str == '\n'
	    || *local_str == '\t'; local_str++);

	/*
	 * Now pointing at next token.
	 */

	ret_string = local_str;
	if (*local_str == '\0') return(NULL);
	*length = 1;

	if (*local_str == '\"')
	{
		for (local_str++; *local_str != '\"'; (*length)++, local_str++);
		(*length)++; local_str++;
	}
	else if (*local_str == ')' || *local_str == '(')
	{
		local_str++;
	}
	else
	{
		for (; *local_str != ' '
		    && *local_str != '\n'
		    && *local_str != '\t'
		    && *local_str != '('
		    && *local_str != ')'; local_str++, (*length)++);
		(*length)--;
	}
	return(ret_string);
}

/*---------------------------------------------------------------------
 * This function fronts the more generic lispRead function by calling
 * lsptok with length == NULL, which just passes lsptok the string.
 *
 * There are two version of lispReadString.
 *
 *      lispReadString (the simple one)
 *           and
 *      lispReadStringWithParams
 *
 * the second one takes en extra (output) argument, 'thisParamListP'
 * If it is non NULL then when the routine returns it will point to
 * a 'ParamLispInfo' which contains information about all the Param
 * nodes of the LispValue returned.
 *
 */

LispValue
lispReadStringWithParams(string, thisParamListP)

char *string;
ParamListInfo *thisParamListP;

{
	LispValue res;

	if (thisParamListP == NULL) {
	    makeParamList = false;	/* global variable */
	}
	else {
	    makeParamList = true;	/* global variable */
	    paramList = NULL;		/* global variable */
	}

	(void) lsptok(string, NULL);
	res = lispRead(true);

	if (thisParamListP != NULL) {
	    *thisParamListP = paramList;
	}
	return(res);
}
			    
LispValue
lispReadString(string)
char *string;
{
    LispValue res;

    res = lispReadStringWithParams(string, NULL);
    return(res);
}

/*-----------------------------------------------------------
 *
 * addToParamList
 *
 * Add an entry for the given Param node to array
 * 'paramList' (thsi is a global variable).
 *
 * 'paramList' points to a place in memory where 'ParamListInfoData'
 * structs are contiguously stored. A struct with its field 'kind'
 * equal to PARAM_INVALID is always the last entry.
 */

static
void
addToParamList(paramNode)
Param paramNode;
{
    int size;
    int oldSize;
    int i;
    int delta = 10;
    ParamListInfo temp;

    if (paramList == NULL) {
	/*
	 * this is the first entry.
	 * allocate some space for a few entries...
	 */
	maxParamListEntries = delta;
	size = sizeof(ParamListInfoData) * maxParamListEntries;
	paramList = (ParamListInfo) palloc(size);
	Assert(PointerIsValid(paramList));
	paramList[0].kind = PARAM_INVALID;
	elog(DEBUG,"added initial entry");
    }

    /*
     * search the array until either another entry with
     * the same name  and paramkind is found
     * or till we reach the end of the array.
     */
    i = 0;
    while(paramList[i].kind != PARAM_INVALID) {
	int match = false;
	if (paramList[i].kind != get_paramkind(paramNode))
	{
	    i++;
	    continue;
	}
	switch (paramList[i].kind) {
	case PARAM_NUM:
	    match = (paramList[i].id == get_paramid(paramNode));
	    break;
	case PARAM_NEW:	
	case PARAM_OLD: {
	    match = NameIsEqual(paramList[i].name, get_paramname(paramNode));
	    if (match && (paramList[i].id != get_paramid(paramNode))) {
		elog(WARN, "param node: same name, different id");
	    }
	    break;
	}
	case PARAM_NAMED:
	default: elog(WARN, "unexpected kind of parameter node");
		break;
	}
	if (match) break;
	i+=1;
    }
/*	    if ((PARAM_NUM != get_paramkind(paramNode)) &&
		!NameIsEqual(paramList[i].name, get_paramname(paramNode))
		&& paramList[i].kind == get_paramkind(paramNode))
		*/


    if (paramList[i].kind != PARAM_INVALID) {
	/*
	 * we have found a duplicate entry
	 */
	return;
    }

    /*
     * No entry exists for this parameter, therefore we must add
     * a  new entry at the end of the array.
     * We must first check to see if the array is big enough
     * to hold this new entry. If not, then we have to reallocate
     * some space.
     */

    if ((i+1) >= maxParamListEntries) {
	oldSize = sizeof(ParamListInfoData) * maxParamListEntries;
	maxParamListEntries += delta;
	size = sizeof(ParamListInfoData) * maxParamListEntries;
	temp = (ParamListInfo) palloc(size);
	Assert(PointerIsValid(temp));
	/*
	 * copy the entries of the old array to the new one
	 */
	bcopy((char *)paramList, (char *)temp, (unsigned int) oldSize);
	pfree((Pointer)paramList);
	paramList = temp;
    }

    /*
     * Ok, there is enough room for a new entry.
     */
    paramList[i].kind = get_paramkind(paramNode);
    paramList[i].name = get_paramname(paramNode); /* Note: no copy! */
    paramList[i].id = get_paramid(paramNode);
    paramList[i].type = get_paramtype(paramNode);
    paramList[i].length = (Size) get_typlen(get_paramtype(paramNode));
    paramList[i].byval = get_typbyval(get_paramtype(paramNode));

    /*
     * mark the next entry in paramList as invalid...
     */
    paramList[i+1].kind = PARAM_INVALID;

}


