/* xlread - xlisp expression input routine */

#include "xlisp.h"

/* external variables */
extern NODE *s_stdout,*true;
extern NODE *s_quote,*s_function,*s_bquote,*s_comma,*s_comat;
extern NODE *xlstack;
extern int xlplevel;

/* external routines */
extern FILE *fopen();

/* forward declarations */
FORWARD NODE *plist();
FORWARD NODE *pstring();
FORWARD NODE *pquote();
FORWARD NODE *pname();

/* xlload - load a file of xlisp expressions */
int xlload(name,vflag,pflag)
  char *name; int vflag,pflag;
{
    NODE *oldstk,fptr,expr;
    char fname[50];
    CONTEXT cntxt;
    int sts;

    /* create a new stack frame */
    oldstk = xlsave(&fptr,&expr,NULL);

    /* allocate a file node */
    fptr.n_ptr = newnode(FPTR);
    fptr.n_ptr->n_fp = NULL;
    fptr.n_ptr->n_savech = 0;

    /* create the file name and print the information line */
    strcpy(fname,name); strcat(fname,".lsp");
    if (vflag)
	printf("; loading \"%s\"\n",fname);

    /* open the file */
    if ((fptr.n_ptr->n_fp = fopen(fname,"r")) == NULL) {
	xlstack = oldstk;
	return (FALSE);
    }

    /* read, evaluate and possibly print each expression in the file */
    xlbegin(&cntxt,CF_ERROR,true);
    if (setjmp(cntxt.c_jmpbuf))
	sts = FALSE;
    else {
	while (xlread(fptr.n_ptr,&expr.n_ptr)) {
	    expr.n_ptr = xleval(expr.n_ptr);
	    if (pflag)
		stdprint(expr.n_ptr);
	}
	sts = TRUE;
    }
    xlend(&cntxt);

    /* close the file */
    fclose(fptr.n_ptr->n_fp);
    fptr.n_ptr->n_fp = NULL;

    /* restore the previous stack frame */
    xlstack = oldstk;

    /* return status */
    return (sts);
}

/* xlread - read an xlisp expression */
int xlread(fptr,pval)
  NODE *fptr,**pval;
{
    /* initialize */
    xlplevel = 0;

    /* parse an expression */
    return (parse(fptr,pval));
}

/* parse - parse an xlisp expression */
LOCAL int parse(fptr,pval)
  NODE *fptr,**pval;
{
    int ch;

    /* keep looking for a node skipping comments */
    while (TRUE)

	/* check next character for type of node */
	switch (ch = nextch(fptr)) {
	case EOF:
		xlgetc(fptr);
		return (FALSE);
	case '\'':			/* a quoted expression */
		xlgetc(fptr);
		*pval = pquote(fptr,s_quote);
		return (TRUE);
	case '#':			/* a quoted function */
		xlgetc(fptr);
		if ((ch = xlgetc(fptr)) == '<')
		    xlfail("unreadable atom");
		else if (ch != '\'')
		    xlfail("expected quote after #");
		*pval = pquote(fptr,s_function);
		return (TRUE);
	case '`':			/* a back quoted expression */
		xlgetc(fptr);
		*pval = pquote(fptr,s_bquote);
		return (TRUE);
	case ',':			/* a comma or comma-at expression */
		xlgetc(fptr);
		if (xlpeek(fptr) == '@') {
		    xlgetc(fptr);
		    *pval = pquote(fptr,s_comat);
		}
		else
		    *pval = pquote(fptr,s_comma);
		return (TRUE);
	case '(':			/* a sublist */
		*pval = plist(fptr);
		return (TRUE);
	case ')':			/* closing paren - shouldn't happen */
		xlfail("extra right paren");
	case '.':			/* dot - shouldn't happen */
		xlfail("misplaced dot");
	case ';':			/* a comment */
		pcomment(fptr);
		break;
	case '"':			/* a string */
		*pval = pstring(fptr);
		return (TRUE);
	default:
		if (issym(ch))		/* a name */
		    *pval = pname(fptr);
		else
		    xlfail("invalid character");
		return (TRUE);
	}
}

/* pcomment - parse a comment */
LOCAL pcomment(fptr)
  NODE *fptr;
{
    int ch;

    /* skip to end of line */
    while ((ch = checkeof(fptr)) != EOF && ch != '\n')
	;
}

/* plist - parse a list */
LOCAL NODE *plist(fptr)
  NODE *fptr;
{
    NODE *oldstk,val,*lastnptr,*nptr,*p;
    int ch;

    /* increment the nesting level */
    xlplevel += 1;

    /* create a new stack frame */
    oldstk = xlsave(&val,NULL);

    /* skip the opening paren */
    xlgetc(fptr);

    /* keep appending nodes until a closing paren is found */
    lastnptr = NULL;
    for (lastnptr = NULL; (ch = nextch(fptr)) != ')'; lastnptr = nptr) {

	/* check for end of file */
	if (ch == EOF)
	    badeof(fptr);

	/* check for a dotted pair */
	if (ch == '.') {

	    /* skip the dot */
	    xlgetc(fptr);

	    /* make sure there's a node */
	    if (lastnptr == NULL)
		xlfail("invalid dotted pair");

	    /* parse the expression after the dot */
	    if (!parse(fptr,&p))
		badeof(fptr);
	    rplacd(lastnptr,p);

	    /* make sure its followed by a close paren */
	    if (nextch(fptr) != ')')
		xlfail("invalid dotted pair");

	    /* done with this list */
	    break;
	}

	/* allocate a new node and link it into the list */
	nptr = newnode(LIST);
	if (lastnptr == NULL)
	    val.n_ptr = nptr;
	else
	    rplacd(lastnptr,nptr);

	/* initialize the new node */
	if (!parse(fptr,&p))
	    badeof(fptr);
	rplaca(nptr,p);
    }

    /* skip the closing paren */
    xlgetc(fptr);

    /* restore the previous stack frame */
    xlstack = oldstk;

    /* decrement the nesting level */
    xlplevel -= 1;

    /* return successfully */
    return (val.n_ptr);
}

/* pstring - parse a string */
LOCAL NODE *pstring(fptr)
  NODE *fptr;
{
    NODE *oldstk,val;
    char sbuf[STRMAX+1];
    int ch,i,d1,d2,d3;

    /* create a new stack frame */
    oldstk = xlsave(&val,NULL);

    /* skip the opening quote */
    xlgetc(fptr);

    /* loop looking for a closing quote */
    for (i = 0; i < STRMAX && (ch = checkeof(fptr)) != '"'; i++) {
	switch (ch) {
	case EOF:
		badeof(fptr);
	case '\\':
		switch (ch = checkeof(fptr)) {
		case 'e':
			ch = '\033';
			break;
		case 'n':
			ch = '\n';
			break;
		case 'r':
			ch = '\r';
			break;
		case 't':
			ch = '\t';
			break;
		default:
			if (ch >= '0' && ch <= '7') {
			    d1 = ch - '0';
			    d2 = checkeof(fptr) - '0';
			    d3 = checkeof(fptr) - '0';
			    ch = (d1 << 6) + (d2 << 3) + d3;
			}
			break;
		}
	}
	sbuf[i] = ch;
    }
    sbuf[i] = 0;

    /* initialize the node */
    val.n_ptr = newnode(STR);
    val.n_ptr->n_str = strsave(sbuf);
    val.n_ptr->n_strtype = DYNAMIC;

    /* restore the previous stack frame */
    xlstack = oldstk;

    /* return the new string */
    return (val.n_ptr);
}

/* pquote - parse a quoted expression */
LOCAL NODE *pquote(fptr,sym)
  NODE *fptr,*sym;
{
    NODE *oldstk,val,*p;

    /* create a new stack frame */
    oldstk = xlsave(&val,NULL);

    /* allocate two nodes */
    val.n_ptr = newnode(LIST);
    rplaca(val.n_ptr,sym);
    rplacd(val.n_ptr,newnode(LIST));

    /* initialize the second to point to the quoted expression */
    if (!parse(fptr,&p))
	badeof(fptr);
    rplaca(cdr(val.n_ptr),p);

    /* restore the previous stack frame */
    xlstack = oldstk;

    /* return the quoted expression */
    return (val.n_ptr);
}

/* pname - parse a symbol name */
LOCAL NODE *pname(fptr)
  NODE *fptr;
{
    char sname[STRMAX+1];
    NODE *val;
    int i;

    /* get symbol name */
    for (i = 0; i < STRMAX && issym(xlpeek(fptr)); )
	sname[i++] = xlgetc(fptr);
    sname[i] = 0;

    /* check for a number or enter the symbol into the oblist */
    return (isnumber(sname,&val) ? val : xlenter(sname,DYNAMIC));
}

/* nextch - look at the next non-blank character */
LOCAL int nextch(fptr)
  NODE *fptr;
{
    int ch;

    /* return and save the next non-blank character */
    while ((ch = xlpeek(fptr)) != EOF && isspace(ch))
	xlgetc(fptr);
    return (ch);
}

/* checkeof - get a character and check for end of file */
LOCAL int checkeof(fptr)
  NODE *fptr;
{
    int ch;

    if ((ch = xlgetc(fptr)) == EOF)
	badeof(fptr);
    return (ch);
}

/* badeof - unexpected eof */
LOCAL badeof(fptr)
  NODE *fptr;
{
    xlgetc(fptr);
    xlfail("unexpected EOF");
}

/* isnumber - check if this string is a number */
int isnumber(str,pval)
  char *str; NODE **pval;
{
    char *p;
    int d;

    /* initialize */
    p = str; d = 0;

    /* check for a sign */
    if (*p == '+' || *p == '-')
	p++;

    /* check for a string of digits */
    while (isdigit(*p))
	p++, d++;

    /* make sure there was at least one digit and this is the end */
    if (d == 0 || *p)
	return (FALSE);

    /* convert the string to an integer and return successfully */
    *pval = newnode(INT);
    (*pval)->n_int = atoi(*str == '+' ? ++str : str);
    return (TRUE);
}

/* issym - check whether a character if valid in a symbol name */
LOCAL int issym(ch)
  int ch;
{
    if (ch <= ' ' || ch >= 0177 ||
    	ch == '(' ||
    	ch == ')' ||
    	ch == ';' || 
	ch == ',' ||
	ch == '`' ||
    	ch == '"' ||
    	ch == '\'')
	return (FALSE);
    else
	return (TRUE);
}
