/* Copyright (C) 1994, 2000 artofcode LLC.  All rights reserved.
  
  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; if not, write to the Free Software Foundation, Inc.,
  59 Temple Place, Suite 330, Boston, MA, 02111-1307.

*/

/*$Id: ztoken.c,v 1.6.6.1.2.1 2003/01/17 00:49:06 giles Exp $ */
/* Token reading operators */
#include "string_.h"
#include "ghost.h"
#include "oper.h"
#include "dstack.h"		/* for dict_find_name */
#include "estack.h"
#include "gsstruct.h"		/* for iscan.h */
#include "stream.h"
#include "files.h"
#include "store.h"
#include "strimpl.h"		/* for sfilter.h */
#include "sfilter.h"		/* for iscan.h */
#include "idict.h"
#include "iname.h"
#include "iscan.h"
#include "itoken.h"		/* for prototypes */

/* <file> token <obj> -true- */
/* <string> token <post> <obj> -true- */
/* <string|file> token -false- */
private int ztoken_continue(P1(i_ctx_t *));
private int token_continue(P4(i_ctx_t *, stream *, scanner_state *, bool));
int
ztoken(i_ctx_t *i_ctx_p)
{
    os_ptr op = osp;

    switch (r_type(op)) {
	default:
	    return_op_typecheck(op);
	case t_file: {
	    stream *s;
	    scanner_state state;

	    check_read_file(s, op);
	    check_ostack(1);
	    scanner_state_init(&state, false);
	    return token_continue(i_ctx_p, s, &state, true);
	}
	case t_string: {
	    ref token;
	    int code = scan_string_token(i_ctx_p, op, &token);

	    switch (code) {
	    case scan_EOF:	/* no tokens */
		make_false(op);
		return 0;
	    default:
		if (code < 0)
		    return code;
	    }
	    push(2);
	    op[-1] = token;
	    make_true(op);
	    return 0;
	}
    }
}
/* Continue reading a token after an interrupt or callout. */
/* *op is the scanner state; op[-1] is the file. */
private int
ztoken_continue(i_ctx_t *i_ctx_p)
{
    os_ptr op = osp;
    stream *s;
    scanner_state *pstate;

    check_read_file(s, op - 1);
    check_stype(*op, st_scanner_state);
    pstate = r_ptr(op, scanner_state);
    pop(1);
    return token_continue(i_ctx_p, s, pstate, false);
}
/* Common code for token reading. */
private int
token_continue(i_ctx_t *i_ctx_p, stream * s, scanner_state * pstate,
	       bool save)
{
    os_ptr op = osp;
    int code;
    ref token;

    /* Note that scan_token may change osp! */
    /* Also, we must temporarily remove the file from the o-stack */
    /* when calling scan_token, in case we are scanning a procedure. */
    ref fref;

    ref_assign(&fref, op);
again:
    pop(1);
    code = scan_token(i_ctx_p, s, &token, pstate);
    op = osp;
    switch (code) {
	default:		/* error */
	    if (code > 0)	/* comment, not possible */
		code = gs_note_error(e_syntaxerror);
	    push(1);
	    ref_assign(op, &fref);
	    break;
	case scan_BOS:
	    code = 0;
	case 0:		/* read a token */
	    push(2);
	    ref_assign(op - 1, &token);
	    make_true(op);
	    break;
	case scan_EOF:		/* no tokens */
	    push(1);
	    make_false(op);
	    code = 0;
	    break;
	case scan_Refill:	/* need more data */
	    push(1);
	    ref_assign(op, &fref);
	    code = scan_handle_refill(i_ctx_p, op, pstate, save, false,
				      ztoken_continue);
	    switch (code) {
		case 0:	/* state is not copied to the heap */
		    goto again;
		case o_push_estack:
		    return code;
	    }
	    break;		/* error */
    }
    if (code <= 0 && !save) {	/* Deallocate the scanner state record. */
	ifree_object(pstate, "token_continue");
    }
    return code;
}

/* <file> .tokenexec - */
/* Read a token and do what the interpreter would do with it. */
/* This is different from token + exec because literal procedures */
/* are not executed (although binary object sequences ARE executed). */
int ztokenexec_continue(P1(i_ctx_t *));	/* export for interpreter */
private int tokenexec_continue(P4(i_ctx_t *, stream *, scanner_state *, bool));
int
ztokenexec(i_ctx_t *i_ctx_p)
{
    os_ptr op = osp;
    stream *s;
    scanner_state state;

    check_read_file(s, op);
    check_estack(1);
    scanner_state_init(&state, false);
    return tokenexec_continue(i_ctx_p, s, &state, true);
}
/* Continue reading a token for execution after an interrupt or callout. */
/* *op is the scanner state; op[-1] is the file. */
/* We export this because this is how the interpreter handles a */
/* scan_Refill for an executable file. */
int
ztokenexec_continue(i_ctx_t *i_ctx_p)
{
    os_ptr op = osp;
    stream *s;
    scanner_state *pstate;

    check_read_file(s, op - 1);
    check_stype(*op, st_scanner_state);
    pstate = r_ptr(op, scanner_state);
    pop(1);
    return tokenexec_continue(i_ctx_p, s, pstate, false);
}
/* Common code for token reading + execution. */
private int
tokenexec_continue(i_ctx_t *i_ctx_p, stream * s, scanner_state * pstate,
		   bool save)
{
    os_ptr op = osp;
    int code;
    /* Note that scan_token may change osp! */
    /* Also, we must temporarily remove the file from the o-stack */
    /* when calling scan_token, in case we are scanning a procedure. */
    ref fref;

    ref_assign(&fref, op);
    pop(1);
again:
    check_estack(1);
    code = scan_token(i_ctx_p, s, (ref *) (esp + 1), pstate);
    op = osp;
    switch (code) {
	case 0:
	    if (r_is_proc(esp + 1)) {	/* Treat procedure as a literal. */
		push(1);
		ref_assign(op, esp + 1);
		code = 0;
		break;
	    }
	    /* falls through */
	case scan_BOS:
	    ++esp;
	    code = o_push_estack;
	    break;
	case scan_EOF:		/* no tokens */
	    code = 0;
	    break;
	case scan_Refill:	/* need more data */
	    code = scan_handle_refill(i_ctx_p, &fref, pstate, save, true,
				      ztokenexec_continue);
	    switch (code) {
		case 0:	/* state is not copied to the heap */
		    goto again;
		case o_push_estack:
		    return code;
	    }
	    break;		/* error */
	case scan_Comment:
	case scan_DSC_Comment:
	    return ztoken_handle_comment(i_ctx_p, &fref, pstate, esp + 1, code,
					 save, true, ztokenexec_continue);
	default:		/* error */
	    break;
    }
    if (code < 0) {		/* Push the operand back on the stack. */
	push(1);
	ref_assign(op, &fref);
    }
    if (!save) {		/* Deallocate the scanner state record. */
	ifree_object(pstate, "token_continue");
    }
    return code;
}

/*
 * Handle a scan_Comment or scan_DSC_Comment return from scan_token
 * (scan_code) by calling out to %Process[DSC]Comment.  The continuation
 * procedure expects the file and scanner state on the o-stack.
 */
int
ztoken_handle_comment(i_ctx_t *i_ctx_p, const ref *fop, scanner_state *sstate,
		      const ref *ptoken, int scan_code,
		      bool save, bool push_file, op_proc_t cont)
{
    const char *proc_name;
    scanner_state *pstate;
    os_ptr op;
    ref *ppcproc;
    int code;

    switch (scan_code) {
    case scan_Comment:
	proc_name = "%ProcessComment";
	break;
    case scan_DSC_Comment:
	proc_name = "%ProcessDSCComment";
	break;
    default:
	return_error(e_Fatal);	/* can't happen */
    }
    /*
     * We can't use check_ostack here, because it returns on overflow.
     */
    /*check_ostack(2);*/
    if (ostop - osp < 2) {
	code = ref_stack_extend(&o_stack, 2);
	if (code < 0)
	    return code;
    }
    check_estack(4);
    code = name_enter_string(proc_name, esp + 4);
    if (code < 0)
	return code;
    if (save) {
	pstate = ialloc_struct(scanner_state, &st_scanner_state,
			       "ztoken_handle_comment");
	if (pstate == 0)
	    return_error(e_VMerror);
	*pstate = *sstate;
    } else
	pstate = sstate;
    /* Save the token now -- it might be on the e-stack. */
    if (!pstate->s_pstack)
	osp[2] = *ptoken;
    /*
     * Push the continuation, scanner state, file, and callout procedure
     * on the e-stack.
     */
    make_op_estack(esp + 1, cont);
    make_istruct(esp + 2, 0, pstate);
    esp[3] = *fop;
    r_clear_attrs(esp + 3, a_executable);
    ppcproc = dict_find_name(esp + 4);
    if (ppcproc == 0) {
	/*
	 * This can only happen during initialization.
	 * Pop the comment string from the o-stack if needed (see below).
	 */
	if (pstate->s_pstack)
	    --osp;
	esp += 3;		/* do run the continuation */
    } else {
	/*
	 * Push the file and comment string on the o-stack.
	 * If we were inside { }, the comment string is already on the stack.
	 */
	if (pstate->s_pstack) {
	    op = ++osp;
	    *op = op[-1];
	} else {
	    op = osp += 2;
	    /* *op = *ptoken; */	/* saved above */
	}
	op[-1] = *fop;
	esp[4] = *ppcproc;
	esp += 4;
    }
    return o_push_estack;
}

/*
 * Update the cached scanner_options in the context state after doing a
 * setuserparams.  (We might move this procedure somewhere else eventually.)
 */
int
ztoken_scanner_options(const ref *upref, int old_options)
{
    typedef struct named_scanner_option_s {
	const char *pname;
	int option;
    } named_scanner_option_t;
    static const named_scanner_option_t named_options[2] = {
	{"ProcessComment", SCAN_PROCESS_COMMENTS},
	{"ProcessDSCComment", SCAN_PROCESS_DSC_COMMENTS}
    };
    int options = old_options;
    int i;

    for (i = 0; i < countof(named_options); ++i) {
	const named_scanner_option_t *pnso = &named_options[i];
	ref *ppcproc;
	int code = dict_find_string(upref, pnso->pname, &ppcproc);

	/* Update the options only if the parameter has changed. */
	if (code >= 0) {
	    if (r_has_type(ppcproc, t_null))
		options &= ~pnso->option;
	    else
		options |= pnso->option;
	}
    }
    return options;
}

/* ------ Initialization procedure ------ */

const op_def ztoken_op_defs[] =
{
    {"1token", ztoken},
    {"1.tokenexec", ztokenexec},
		/* Internal operators */
    {"2%ztokenexec_continue", ztokenexec_continue},
    op_def_end(0)
};
