/* Copyright (C) 1989, 1995, 1997, 1998, 1999, 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: iinit.c,v 1.3.4.1.2.1 2003/01/17 00:49:04 giles Exp $ */
/* Initialize internally known objects for Ghostscript interpreter */
#include "string_.h"
#include "ghost.h"
#include "gscdefs.h"
#include "gsexit.h"
#include "gsstruct.h"
#include "errors.h"
#include "ialloc.h"
#include "iddict.h"
#include "dstack.h"
#include "ilevel.h"
#include "iinit.h"
#include "iname.h"
#include "interp.h"
#include "ipacked.h"
#include "iparray.h"
#include "iutil.h"
#include "ivmspace.h"
#include "opdef.h"
#include "store.h"

/* Implementation parameters. */
/*
 * Define the (initial) sizes of the various system dictionaries.  We want
 * the sizes to be prime numbers large enough to cover all the operators,
 * plus everything in the init files, even if all the optional features are
 * selected.  Note that these sizes must be large enough to get us through
 * initialization, since we start up in Level 1 mode where dictionaries
 * don't expand automatically.
 */
/* The size of systemdict can be set in the makefile. */
#ifndef SYSTEMDICT_SIZE
#  define SYSTEMDICT_SIZE 631
#endif
#ifndef SYSTEMDICT_LEVEL2_SIZE
#  define SYSTEMDICT_LEVEL2_SIZE 983
#endif
#ifndef SYSTEMDICT_LL3_SIZE
#  define SYSTEMDICT_LL3_SIZE 1123
#endif
/* The size of level2dict, if applicable, can be set in the makefile. */
#ifndef LEVEL2DICT_SIZE
#  define LEVEL2DICT_SIZE 233
#endif
/* Ditto the size of ll3dict. */
#ifndef LL3DICT_SIZE
#  define LL3DICT_SIZE 43
#endif
/* Ditto the size of filterdict. */
#ifndef FILTERDICT_SIZE
#  define FILTERDICT_SIZE 43
#endif
/* Define an arbitrary size for the operator procedure tables. */
#ifndef OP_ARRAY_TABLE_SIZE
#  define OP_ARRAY_TABLE_SIZE 180
#endif
#ifndef OP_ARRAY_TABLE_GLOBAL_SIZE
#  define OP_ARRAY_TABLE_GLOBAL_SIZE OP_ARRAY_TABLE_SIZE
#endif
#ifndef OP_ARRAY_TABLE_LOCAL_SIZE
#  define OP_ARRAY_TABLE_LOCAL_SIZE (OP_ARRAY_TABLE_SIZE / 2)
#endif
#define OP_ARRAY_TABLE_TOTAL_SIZE\
  (OP_ARRAY_TABLE_GLOBAL_SIZE + OP_ARRAY_TABLE_LOCAL_SIZE)

/* Define the list of error names. */
const char *const gs_error_names[] =
{
    ERROR_NAMES
};

/* The operator tables */
op_array_table op_array_table_global, op_array_table_local;	/* definitions of `operator' procedures */

/* Enter a name and value into a dictionary. */
private int
i_initial_enter_name_in(i_ctx_t *i_ctx_p, ref *pdict, const char *nstr,
			const ref * pref)
{
    int code = idict_put_string(pdict, nstr, pref);

    if (code < 0)
	lprintf4("initial_enter failed (%d), entering /%s in -dict:%u/%u-\n",
		 code, nstr, dict_length(pdict), dict_maxlength(pdict));
    return code;
}
int
i_initial_enter_name(i_ctx_t *i_ctx_p, const char *nstr, const ref * pref)
{
    return i_initial_enter_name_in(i_ctx_p, systemdict, nstr, pref);
}

/* Remove a name from systemdict. */
void
i_initial_remove_name(i_ctx_t *i_ctx_p, const char *nstr)
{
    ref nref;

    if (name_ref((const byte *)nstr, strlen(nstr), &nref, -1) >= 0)
	idict_undef(systemdict, &nref);
}

/* Define the names and sizes of the initial dictionaries. */
/* The names are used to create references in systemdict. */
const struct {
    const char *name;
    uint size;
    bool local;
} initial_dictionaries[] = {
#ifdef INITIAL_DICTIONARIES
    INITIAL_DICTIONARIES
#else
    /* systemdict is created and named automagically */
    {
	"level2dict", LEVEL2DICT_SIZE, false
    },
    {
	"ll3dict", LL3DICT_SIZE, false
    },
    {
	"globaldict", 0, false
    },
    {
	"userdict", 0, true
    },
    {
	"filterdict", FILTERDICT_SIZE, false
    },
#endif
};
/* systemdict and globaldict are magically inserted at the bottom */
const char *const initial_dstack[] =
{
#ifdef INITIAL_DSTACK
    INITIAL_DSTACK
#else
    "userdict"
#endif
};

#define MIN_DSTACK_SIZE (countof(initial_dstack) + 1)	/* +1 for systemdict */


/*
 * Detect whether we have any Level 2 or LanguageLevel 3 operators.
 * We export this for gs_init1 in imain.c.
 * This is slow, but we only call it a couple of times.
 */
private int
gs_op_language_level(void)
{
    const op_def *const *tptr;
    int level = 1;

    for (tptr = op_defs_all; *tptr != 0; ++tptr) {
	const op_def *def;

	for (def = *tptr; def->oname != 0; ++def)
	    if (op_def_is_begin_dict(def)) {
		if (!strcmp(def->oname, "level2dict"))
		    level = max(level, 2);
		else if (!strcmp(def->oname, "ll3dict"))
		    level = max(level, 3);
	    }
    }
    return level;
}
bool
gs_have_level2(void)
{
    return (gs_op_language_level() >= 2);
}

/* Create an initial dictionary if necessary. */
private ref *
make_initial_dict(i_ctx_t *i_ctx_p, const char *iname, ref idicts[])
{
    int i;

    /* systemdict was created specially. */
    if (!strcmp(iname, "systemdict"))
	return systemdict;
    for (i = 0; i < countof(initial_dictionaries); i++) {
	const char *dname = initial_dictionaries[i].name;
	const int dsize = initial_dictionaries[i].size;

	if (!strcmp(iname, dname)) {
	    ref *dref = &idicts[i];

	    if (r_has_type(dref, t_null)) {
		gs_ref_memory_t *mem =
		    (initial_dictionaries[i].local ?
		     iimemory_local : iimemory_global);
		int code = dict_alloc(mem, dsize, dref);

		if (code < 0)
		    return 0;	/* disaster */
	    }
	    return dref;
	}
    }

    /*
     * Name mentioned in some op_def, but not in initial_dictionaries.
     * Punt.
     */
    return 0;
}

/* Initialize objects other than operators.  In particular, */
/* initialize the dictionaries that hold operator definitions. */
int
obj_init(i_ctx_t **pi_ctx_p, gs_dual_memory_t *idmem)
{
    int level = gs_op_language_level();
    ref system_dict;
    i_ctx_t *i_ctx_p;
    int code;

    /*
     * Create systemdict.  The context machinery requires that
     * we do this before initializing the interpreter.
     */
    code = dict_alloc(idmem->space_global,
		      (level >= 3 ? SYSTEMDICT_LL3_SIZE :
		       level >= 2 ? SYSTEMDICT_LEVEL2_SIZE : SYSTEMDICT_SIZE),
		      &system_dict);
    if (code < 0)
	return code;

    /* Initialize the interpreter. */
    code = gs_interp_init(pi_ctx_p, &system_dict, idmem);
    if (code < 0)
	return code;
    i_ctx_p = *pi_ctx_p;

    {
#define icount countof(initial_dictionaries)
	ref idicts[icount];
	int i;
	const op_def *const *tptr;

	min_dstack_size = MIN_DSTACK_SIZE;

	refset_null(idicts, icount);

	/* Put systemdict on the dictionary stack. */
	if (level >= 2) {
	    dsp += 2;
	    /*
	     * For the moment, let globaldict be an alias for systemdict.
	     */
	    dsp[-1] = system_dict;
	    min_dstack_size++;
	} else {
	    ++dsp;
	}
	*dsp = system_dict;

	/* Create dictionaries which are to be homes for operators. */
	for (tptr = op_defs_all; *tptr != 0; tptr++) {
	    const op_def *def;

	    for (def = *tptr; def->oname != 0; def++)
		if (op_def_is_begin_dict(def)) {
		    if (make_initial_dict(i_ctx_p, def->oname, idicts) == 0)
			return_error(e_VMerror);
		}
	}

	/* Set up the initial dstack. */
	for (i = 0; i < countof(initial_dstack); i++) {
	    const char *dname = initial_dstack[i];

	    ++dsp;
	    if (!strcmp(dname, "userdict"))
		dstack_userdict_index = dsp - dsbot;
	    ref_assign(dsp, make_initial_dict(i_ctx_p, dname, idicts));
	}

	/* Enter names of referenced initial dictionaries into systemdict. */
	initial_enter_name("systemdict", systemdict);
	for (i = 0; i < icount; i++) {
	    ref *idict = &idicts[i];

	    if (!r_has_type(idict, t_null)) {
		/*
		 * Note that we enter the dictionary in systemdict
		 * even if it is in local VM.  There is a special
		 * provision in the garbage collector for this:
		 * see ivmspace.h for more information.
		 * In order to do this, we must temporarily
		 * identify systemdict as local, so that the
		 * store check in dict_put won't fail.
		 */
		uint save_space = r_space(systemdict);

		r_set_space(systemdict, avm_local);
		code = initial_enter_name(initial_dictionaries[i].name,
					  idict);
		r_set_space(systemdict, save_space);
		if (code < 0)
		    return code;
	    }
	}
#undef icount
    }

    gs_interp_reset(i_ctx_p);

    {
	ref vnull, vtrue, vfalse;

	make_null(&vnull);
	make_true(&vtrue);
	make_false(&vfalse);
	if ((code = initial_enter_name("null", &vnull)) < 0 ||
	    (code = initial_enter_name("true", &vtrue)) < 0 ||
	    (code = initial_enter_name("false", &vfalse)) < 0
	    )
	    return code;
    }

    /* Create the error name table */
    {
	int n = countof(gs_error_names) - 1;
	int i;
	ref era;

	code = ialloc_ref_array(&era, a_readonly, n, "ErrorNames");
	if (code < 0)
	    return code;
	for (i = 0; i < n; i++)
	    if ((code = name_enter_string((const char *)gs_error_names[i],
					  era.value.refs + i)) < 0)
		return code;
	return initial_enter_name("ErrorNames", &era);
    }
}

/* Run the initialization procedures of the individual operator files. */
int
zop_init(i_ctx_t *i_ctx_p)
{
    const op_def *const *tptr;
    int code;

    /* Because of a bug in Sun's SC1.0 compiler, */
    /* we have to spell out the typedef for op_def_ptr here: */
    const op_def *def;

    for (tptr = op_defs_all; *tptr != 0; tptr++) {
	for (def = *tptr; def->oname != 0; def++)
	    DO_NOTHING;
	if (def->proc != 0) {
	    code = def->proc(i_ctx_p);
	    if (code < 0) {
		lprintf2("op_init proc 0x%lx returned error %d!\n",
			 (ulong)def->proc, code);
		return code;
	    }
	}
    }

    /* Initialize the predefined names other than operators. */
    /* Do this here in case op_init changed any of them. */
    {
	ref vcr, vpr, vpf, vre, vrd;

	make_const_string(&vcr, a_readonly | avm_foreign,
			  strlen(gs_copyright), (const byte *)gs_copyright);
	make_const_string(&vpr, a_readonly | avm_foreign,
			  strlen(gs_product), (const byte *)gs_product);
	make_const_string(&vpf, a_readonly | avm_foreign,
			  strlen(gs_productfamily),
			  (const byte *)gs_productfamily);
	make_int(&vre, gs_revision);
	make_int(&vrd, gs_revisiondate);
	if ((code = initial_enter_name("copyright", &vcr)) < 0 ||
	    (code = initial_enter_name("product", &vpr)) < 0 ||
	    (code = initial_enter_name("productfamily", &vpf)) < 0 ||
	    (code = initial_enter_name("revision", &vre)) < 0 ||
	    (code = initial_enter_name("revisiondate", &vrd)) < 0)
	    return code;
    }

    return 0;
}

/* Create an op_array table. */
private int
alloc_op_array_table(i_ctx_t *i_ctx_p, uint size, uint space,
		     op_array_table *opt)
{
    uint save_space = ialloc_space(idmemory);
    int code;

    ialloc_set_space(idmemory, space);
    code = ialloc_ref_array(&opt->table, a_readonly, size,
			    "op_array table");
    ialloc_set_space(idmemory, save_space);
    if (code < 0)
	return code;
    refset_null(opt->table.value.refs, size);
    opt->nx_table =
	(ushort *) ialloc_byte_array(size, sizeof(ushort),
				     "op_array nx_table");
    if (opt->nx_table == 0)
	return_error(e_VMerror);
    opt->count = 0;
    opt->root_p = &opt->table;
    opt->attrs = space | a_executable;
    return 0;
}

/* Initialize the operator table. */
int
op_init(i_ctx_t *i_ctx_p)
{
    const op_def *const *tptr;
    int code;

    /* Enter each operator into the appropriate dictionary. */

    for (tptr = op_defs_all; *tptr != 0; tptr++) {
	ref *pdict = systemdict;
	const op_def *def;
	const char *nstr;

	for (def = *tptr; (nstr = def->oname) != 0; def++)
	    if (op_def_is_begin_dict(def)) {
		ref nref;

		code = name_ref((const byte *)nstr, strlen(nstr), &nref, -1);
		if (code < 0)
		    return code;
		if (!dict_find(systemdict, &nref, &pdict))
		    return_error(e_Fatal);
		if (!r_has_type(pdict, t_dictionary))
		    return_error(e_Fatal);
	    } else {
		ref oper;
		uint index_in_table = def - *tptr;
		uint opidx = (tptr - op_defs_all) * OP_DEFS_MAX_SIZE +
		    index_in_table;

		if (index_in_table >= OP_DEFS_MAX_SIZE)
		    dprintf1("opdef overrun: %s\n", def->oname);
		gs_interp_make_oper(&oper, def->proc, opidx);
		/* The first character of the name is a digit */
		/* giving the minimum acceptable number of operands. */
		/* Check to make sure it's within bounds. */
		if (*nstr - '0' > gs_interp_max_op_num_args)
		    return_error(e_Fatal);
		nstr++;
		/*
		 * Skip internal operators, and the second occurrence of
		 * operators with special indices.
		 */
		if (*nstr != '%' && r_size(&oper) == opidx) {
		    code =
			i_initial_enter_name_in(i_ctx_p, pdict, nstr, &oper);
		    if (code < 0)
			return code;
		}
	    }
    }
    /* Allocate the tables for `operator' procedures. */
    /* Make one of them local so we can have local operators. */

    if ((code = alloc_op_array_table(i_ctx_p, OP_ARRAY_TABLE_GLOBAL_SIZE,
				     avm_global, &op_array_table_global) < 0))
	return code;
    op_array_table_global.base_index = op_def_count;
    if ((code = gs_register_ref_root(imemory, NULL,
				     (void **)&op_array_table_global.root_p,
				     "op_array_table(global)")) < 0 ||
	(code = gs_register_struct_root(imemory, NULL,
				(void **)&op_array_table_global.nx_table,
					"op_array nx_table(global)")) < 0 ||
	(code = alloc_op_array_table(i_ctx_p, OP_ARRAY_TABLE_LOCAL_SIZE,
				     avm_local, &op_array_table_local) < 0)
	)
	return code;
    op_array_table_local.base_index =
	op_array_table_global.base_index +
	r_size(&op_array_table_global.table);
    if ((code = gs_register_ref_root(imemory, NULL,
				     (void **)&op_array_table_local.root_p,
				     "op_array_table(local)")) < 0 ||
	(code = gs_register_struct_root(imemory, NULL,
				(void **)&op_array_table_local.nx_table,
					"op_array nx_table(local)")) < 0
	)
	return code;

    return 0;
}
