/* Copyright (C) 1989, 1995 Aladdin Enterprises.  All rights reserved.
  
  This file is part of Aladdin Ghostscript.
  
  Aladdin Ghostscript is distributed with NO WARRANTY OF ANY KIND.  No author
  or distributor accepts any responsibility for the consequences of using it,
  or for whether it serves any particular purpose or works at all, unless he
  or she says so in writing.  Refer to the Aladdin Ghostscript Free Public
  License (the "License") for full details.
  
  Every copy of Aladdin Ghostscript must include a copy of the License,
  normally in a plain ASCII text file named PUBLIC.  The License grants you
  the right to copy, modify and redistribute Aladdin Ghostscript, but only
  under certain conditions described in the License.  Among other things, the
  License requires that the copyright notice and this notice be preserved on
  all copies.
*/

/* gs.c */
/* Driver program for Ghostscript */
/* Define PROGRAM_NAME before we include std.h */
#define PROGRAM_NAME gs_product
#include "ctype_.h"
#include "memory_.h"
#include "string_.h"
/* Capture stdin/out/err before gs.h redefines them. */
#include <stdio.h>
static FILE *real_stdin, *real_stdout, *real_stderr;
static void
get_real(void)
{	real_stdin = stdin, real_stdout = stdout, real_stderr = stderr;
}
#include "ghost.h"
#include "gscdefs.h"
#include "gxdevice.h"
#include "gxdevmem.h"
#include "gsdevice.h"
#include "stream.h"
#include "errors.h"
#include "estack.h"
#include "ialloc.h"
#include "strimpl.h"		/* for sfilter.h */
#include "sfilter.h"		/* for iscan.h */
#include "ostack.h"		/* must precede iscan.h */
#include "iscan.h"
#include "main.h"
#include "store.h"
#include "files.h"				/* requires stream.h */
#include "interp.h"
#include "iutil.h"
#include "ivmspace.h"

/* Define an optional array of strings for testing. */
/*#define RUN_STRINGS*/
#ifdef RUN_STRINGS
private const char *run_strings[] = {
  "2 vmreclaim /SAVE save def 2 vmreclaim",
  "(saved\n) print flush",
  "SAVE restore (restored\n) print flush 2 vmreclaim",
  "(done\n) print flush quit",
  0
};
#endif


/* Import operator procedures */
extern int zflush (P1(os_ptr));
extern int zflushpage (P1(os_ptr));

#ifndef GS_LIB
#  define GS_LIB "GS_LIB"
#endif

#ifndef GS_OPTIONS
#  define GS_OPTIONS "GS_OPTIONS"
#endif

/* Library routines not declared in a standard header */
extern char *getenv(P1(const char *));
/* Note: sscanf incorrectly defines its first argument as char * */
/* rather than const char *.  This accounts for the ugly casts below. */

/* Other imported data */
extern const char *gs_doc_directory;
extern ref gs_emulator_name_array[];

/*
 * Help strings.  We have to break them up into parts, because
 * the Watcom compiler has a limit of 510 characters for a single token.
 * For PC displays, we want to limit the strings to 24 lines.
 */
private const char far_data gs_help1a[] = "\
Usage: gs [switches] [file1.ps file2.ps ...]\n\
Available devices:";
private const char far_data gs_help1b[] = "\n\
Language interpreters:";
private const char far_data gs_help1c[] = "\n\
Search path:";
private const char far_data gs_help2a[] = "\n\
Most frequently used switches: (you can use # in place of =)\n\
    -c quit              (as the last switch) exit after processing files\n\
    -d<name>[=<token>]   define name as token, or true if no token given\n\
    -dNOPAUSE            don't pause between pages\n\
    -g<width>x<height>   set width and height (`geometry'), in pixels\n\
    -q                   `quiet' mode, suppress most messages\n\
    -r<res>              set resolution, in pixels per inch\n";
private const char far_data gs_help2b[] = "\
    -s<name>=<string>    define name as string\n\
    -sDEVICE=<devname>   select initial device\n\
    -sOutputFile=<file>  select output file: embed %%d or %%ld for page #,\n\
                           - means stdout, use |command to pipe\n\
    -                    read from stdin (e.g., a pipe) non-interactively\n\
For more information, see the (plain text) file  use.doc  in the directory\n\
	%s.\n";

/* Forward references */
typedef struct arg_list_s arg_list;
private int swproc(P3(gs_main_instance *, const char *, arg_list *));
private void argproc(P2(gs_main_instance *, const char *));
private void print_help(P2(gs_main_instance *, bool));
private void print_revision(P0());
private void print_version(P0());
private int esc_strlen(P1(const char *));
private void esc_strcat(P2(char *, const char *));
private void runarg(P6(gs_main_instance *, const char *, const char *, const char *, bool, bool));
private void run_string(P3(gs_main_instance *, const char *, bool));
private void run_finish(P3(int, int, ref *));

/* Parameters set by swproc */
private bool batch;

/* ------ Argument management ------ */

/* We need to handle recursion into @-files. */
/* The following structures keep track of the state. */
typedef struct arg_source_s {
	int is_file;
	union _u {
		const char *str;
		FILE *file;
	} u;
} arg_source;
struct arg_list_s {
	bool expand_ats;	/* if true, expand @-files */
	const char **argp;
	int argn;
	int depth;		/* depth of @-files */
#define cstr_max 128
	char cstr[cstr_max + 1];
#define csource_max 10
	arg_source sources[csource_max];
};

/* Initialize an arg list. */
private void
arg_init(arg_list *pal, const char **argv, int argc)
{	pal->expand_ats = true;
	pal->argp = argv + 1;
	pal->argn = argc - 1;
	pal->depth = 0;
}

/* Push a string onto an arg list. */
/* (Only used at top level, so no need to check depth.) */
private void
arg_push_string(arg_list *pal, const char *str)
{	arg_source *pas = &pal->sources[pal->depth];
	pas->is_file = 0;
	pas->u.str = str;
	pal->depth++;
}

/* Clean up an arg list. */
private void
arg_finit(arg_list *pal)
{	while ( pal->depth )
	  if ( pal->sources[--(pal->depth)].is_file )
		fclose(pal->sources[pal->depth].u.file);
}

/* Get the next arg from a list. */
/* Note that these are not copied to the heap. */
private const char *
arg_next(gs_main_instance *minst, arg_list *pal)
{	arg_source *pas;
	FILE *f;
	const char *astr = 0;		/* initialized only to pacify gcc */
	char *cstr;
	const char *result;
	int endc;
	register int c;
	register int i;
	bool in_quote;

top:	pas = &pal->sources[pal->depth - 1];
	if ( pal->depth == 0 )
	{	if ( pal->argn == 0 )		/* all done */
			return 0;
		pal->argn--;
		result = *(pal->argp++);
		goto at;
	}
	if ( pas->is_file )
		f = pas->u.file, endc = EOF;
	else
		astr = pas->u.str, f = NULL, endc = 0;
	result = cstr = pal->cstr;
#define cfsgetc() (f == NULL ? (*astr ? *astr++ : 0) : fgetc(f))
	while ( isspace(c = cfsgetc()) ) ;
	if ( c == endc )
	{	if ( f != NULL )
			fclose(f);
		pal->depth--;
		goto top;
	}
	in_quote = false;
	for ( i = 0; ; )
	{	if ( i == cstr_max - 1 )
		{	cstr[i] = 0;
			fprintf(stdout, "Command too long: %s\n", cstr);
			gs_exit(1);
		}
		/* If input is coming from an @-file, allow quotes */
		/* to protect whitespace. */
		if ( c == '"' && f != NULL )
		  in_quote = !in_quote;
		else
		  cstr[i++] = c;
		c = cfsgetc();
		if ( c == endc )
		  {	if ( in_quote )
			  { cstr[i] = 0;
			    fprintf(stdout,
				    "Unterminated quote in @-file: %s\n",
				    cstr);
			    gs_exit(1);
			  }
			break;
		  }
		if ( isspace(c) && !in_quote )
			break;
	}
	cstr[i] = 0;
	if ( f == NULL )
		pas->u.str = astr;
at:	if ( pal->expand_ats && result[0] == '@' )
	{	if ( pal->depth == csource_max )
		{	lprintf("Too much nesting of @-files.\n");
			gs_exit(1);
		}
		gs_main_set_lib_paths(minst);
		result++;		/* skip @ */
		f = lib_fopen(result);
		if ( f == NULL )
		{	fprintf(stdout, "Unable to open command line file %s\n", result);
			gs_exit(1);
		}
		pal->depth++;
		pas++;
		pas->is_file = 1;
		pas->u.file = f;
		goto top;
	}
	return result;
}

/* Copy an argument string to the heap. */
private char *
arg_copy(const char *str)
{	char *sstr = gs_malloc(strlen(str) + 1, 1, "arg_copy");
	if ( sstr == 0 )
	{	lprintf("Out of memory!\n");
		gs_exit(1);
	}
	strcpy(sstr, str);
	return sstr;
}

/* ------ Main program ------ */

int
main(int argc, const char *argv[])
{	const char *arg;
	arg_list args;
	gs_main_instance *minst = gs_main_instance_default();

	get_real();
	arg_init(&args, argv, argc);
	gs_main_init0(minst, real_stdin, real_stdout, real_stderr, argc);
	   {	char *lib = getenv(GS_LIB);
		if ( lib != 0 ) 
		   {	int len = strlen(lib);
			char *path = gs_malloc(len + 1, 1, "GS_LIB");
			strcpy(path, lib);
			minst->lib_env_path = path;
		   }
	   }
	/* Prescan the command line for --help and --version. */
	{ int i;
	  for ( i = 1; i < argc; ++i )
	    if ( !strcmp(argv[i], "--" ) )
	      { /* A PostScript program will be interpreting all the */
		/* remaining switches, so stop scanning. */
		break;
	      }
	    else if ( !strcmp(argv[i], "--help") )
	      { print_help(minst, true);
		gs_exit(0);
	      }
	    else if ( !strcmp(argv[i], "--version") )
	      { print_version();
		fprintf(stdout, "\n");
		gs_exit(0);
	      }
	}
	/* Execute files named in the command line, */
	/* processing options along the way. */
	/* Wait until the first file name (or the end */
	/* of the line) to finish initialization. */
	batch = false;
	{	const char *opts = getenv(GS_OPTIONS);
		if ( opts != 0 )
			arg_push_string(&args, opts);
	}
	while ( (arg = arg_next(minst, &args)) != 0 )
	   {	switch ( *arg )
		{
		case '-':
			if ( swproc(minst, arg, &args) < 0 )
			  fprintf(stdout,
				  "Unknown switch %s - ignoring\n", arg);
			break;
		default:
			argproc(minst, arg);
		}
	   }
	gs_main_init2(minst);

#ifdef RUN_STRINGS
	{	/* Run a list of strings (for testing). */
		const char **pstr = run_strings;
		for ( ; *pstr; ++pstr )
		  {	int exit_code;
			ref error_object;
			int code;
			fprintf(stdout, "{%s} =>\n", *pstr);
			fflush(stdout);
			code = gs_main_run_string(minst, *pstr, 0,
						  &exit_code, &error_object);
			zflush(osp);
			fprintf(stdout, " => code = %d\n", code);
			fflush(stdout);
			if ( code < 0 )
			  gs_exit(1);
		  }
	}
#endif

	if ( !batch )
	  run_string(minst, "systemdict /start get exec", true);
	gs_exit(0);			/* exit */
	/* NOTREACHED */
}

/* Process switches */
private int
swproc(gs_main_instance *minst, const char *arg, arg_list *pal)
{	char sw = arg[1];
	ref vtrue;

	make_true(&vtrue);
	arg += 2;		/* skip - and letter */
	switch ( sw )
	   {
	default:
		return -1;
	case 0:				/* read stdin as a file */
		batch = true;
		/* Set NOPAUSE so showpage won't try to read from stdin. */
		swproc(minst, "-dNOPAUSE=true", pal);
		gs_main_init2(minst);		/* Finish initialization */
		/* We delete this only to make Ghostview work properly. */
		/**************** This is WRONG. ****************/
		/*gs_stdin_is_interactive = false;*/
		run_string(minst, ".runstdin", true);
		break;
	case '-':			/* run with command line args */
	case '+':
		pal->expand_ats = false;
	case '@':			/* ditto with @-expansion */
	   {	const char *psarg = arg_next(minst, pal);
		if ( psarg == 0 )
		{	fprintf(stdout, "Usage: gs ... -%c file.ps arg1 ... argn", sw);
			arg_finit(pal);
			gs_exit(1);
		}
		psarg = arg_copy(psarg);
		gs_main_init2(minst);
		run_string(minst, "userdict/ARGUMENTS[", false);
		while ( (arg = arg_next(minst, pal)) != 0 )
		  runarg(minst, "", arg_copy(arg), "", true, false);
		runarg(minst, "]put", psarg, ".runfile", true, true);
		gs_exit(0);
	   }
	case 'A':			/* trace allocator */
		gs_alloc_debug = 1; break;
	case 'c':			/* code follows */
	  {	bool ats = pal->expand_ats;
		gs_main_init2(minst);
		pal->expand_ats = false;
		while ( (arg = arg_next(minst, pal)) != 0 )
		  {	char *sarg;
			if ( arg[0] == '@' ||
			     (arg[0] == '-' && !isdigit(arg[1]))
			   )
			  break;
			sarg = arg_copy(arg);
			runarg(minst, "", sarg, ".runstring", false, false);
		  }
		if ( arg != 0 )
		  arg_push_string(pal, arg_copy(arg));
		pal->expand_ats = ats;
		break;
	  }
	case 'E':			/* log errors */
		gs_log_errors = 1; break;
	case 'f':			/* run file of arbitrary name */
		if ( *arg != 0 )
		  argproc(minst, arg);
		break;
	case 'g':			/* define device geometry */
	   {	long width, height;
		ref value;
		gs_main_init1(minst);
		if ( sscanf((const char *)arg, "%ldx%ld", &width, &height) != 2 )
		   {	puts("-g must be followed by <width>x<height>");
			gs_exit(1);
		   }
		make_int(&value, width);
		initial_enter_name("DEVICEWIDTH", &value);
		make_int(&value, height);
		initial_enter_name("DEVICEHEIGHT", &value);
		initial_enter_name("FIXEDMEDIA", &vtrue);
		break;
	   }
	case 'h':			/* print help */
	case '?':			/* ditto */
		print_help(minst, false);
		gs_exit(0);
	case 'I':			/* specify search path */
		gs_main_add_lib_path(minst, arg_copy(arg));
		break;
	case 'M':			/* set memory allocation increment */
	   {	unsigned msize = 0;
		sscanf((const char *)arg, "%d", &msize);
#if arch_ints_are_short
		if ( msize <= 0 || msize >= 64 )
		   {	puts("-M must be between 1 and 63");
			gs_exit(1);
		   }
#endif
		minst->memory_chunk_size = msize << 10;
	   }
		break;
	case 'N':			/* set size of name table */
	   {	unsigned nsize = 0;
		sscanf((const char *)arg, "%d", &nsize);
#if arch_ints_are_short
		if ( nsize < 2 || nsize > 64 )
		   {	puts("-N must be between 2 and 64");
			gs_exit(1);
		   }
#endif
		minst->name_table_size = (ulong)nsize << 10;
	   }
		break;
	case 'P':			/* choose whether search '.' first */
		if ( !strcmp(arg, "") )
		  minst->search_here_first = true;
		else if ( !strcmp(arg, "-") )
		  minst->search_here_first = false;
		else
		  {	puts("Only -P or -P- is allowed.");
			gs_exit(1);
		  }
		break;
	case 'q':			/* quiet startup */
		gs_main_init1(minst);
		initial_enter_name("QUIET", &vtrue);
		break;
	case 'r':			/* define device resolution */
	   {	float xres, yres;
		ref value;
		gs_main_init1(minst);
		switch ( sscanf((const char *)arg, "%fx%f", &xres, &yres) )
		   {
		default:
			puts("-r must be followed by <res> or <xres>x<yres>");
			gs_exit(1);
		case 1:			/* -r<res> */
			yres = xres;
		case 2:			/* -r<xres>x<yres> */
			make_real(&value, xres);
			initial_enter_name("DEVICEXRESOLUTION", &value);
			make_real(&value, yres);
			initial_enter_name("DEVICEYRESOLUTION", &value);
			initial_enter_name("FIXEDRESOLUTION", &vtrue);
		   }
		break;
	   }
	case 'D':			/* define name */
	case 'd':
	case 'S':			/* define name as string */
	case 's':
	   {	char *adef = arg_copy(arg);
		char *eqp = strchr(adef, '=');
		bool isd = (sw == 'D' || sw == 'd');
		ref value;

		if ( eqp == NULL )
			eqp = strchr(adef, '#');
		/* Initialize the object memory, scanner, and */
		/* name table now if needed. */
		gs_main_init1(minst);
		if ( eqp == adef )
		   {	puts("Usage: -dname, -dname=token, -sname=string");
			gs_exit(1);
		   }
		if ( eqp == NULL )
		   {	if ( isd )
				make_true(&value);
			else
				make_empty_string(&value, a_readonly);
		   }
		else
		   {	int code;
			uint space = icurrent_space;

			*eqp++ = 0;
			ialloc_set_space(idmemory, avm_system);
			if ( isd )
			   {	stream astream;
				scanner_state state;
				sread_string(&astream,
					     (const byte *)eqp, strlen(eqp));
				scanner_state_init(&state, false);
				code = scan_token(&astream, &value, &state);
				if ( code )
				   {	puts("-dname= must be followed by a valid token");
					gs_exit(1);
				   }
			   }
			else
			   {	int len = strlen(eqp);
				char *str = gs_malloc((uint)len, 1, "-s");
				if ( str == 0 )
				   {	lprintf("Out of memory!\n");
					gs_exit(1);
				   }
				memcpy(str, eqp, len);
				make_const_string(&value,
					a_readonly | avm_foreign,
					len, (const byte *)str);
			   }
			ialloc_set_space(idmemory, space);
		   }
		/* Enter the name in systemdict. */
		initial_enter_name(adef, &value);
		break;
	   }
	case 'u':			/* undefine name */
		if ( !*arg )
		  { puts("-u requires a name to undefine.");
		    gs_exit(1);
		  }
		gs_main_init1(minst);
		initial_remove_name(arg);
		break;
	case 'v':			/* print revision */
		print_revision();
		gs_exit(0);
/*#ifdef DEBUG*/
	/*
	 * Here we provide a place for inserting debugging code that can be
	 * run in place of the normal interpreter code.
	 */
	case 'X':
	  {
#include "gscoord.h"
#include "gspath.h"
#include "gsstate.h"
extern gs_state *igs;
		int xec;		/* exit_code */
		ref xeo;		/* error_object */
		long nreps = 1;
		float step = 6;
		int rep;
		int code;

		gs_init2();
#define start_x()\
  gs_main_run_string_begin(minst, 1, &xec, &xeo)
#define run_x(str)\
  gs_main_run_string_continue(minst, str, strlen(str), 1, &xec, &xeo)
#define stop_x()\
  gs_main_run_string_end(minst, 1, &xec, &xeo)

		gs_setrgbcolor(igs, 1.0, 0.0, 0.0);
		gs_setalpha(igs, 0.75);
		gs_setlinewidth(igs, 15.0);

		start_x();
		run_x("/Font where{pop}{/Font/Courier def}ifelse Font\n");
		run_x("/Size where{pop}{/Size 36 def}ifelse Size selectfont\n");

		run_x("matrix currentmatrix 0 get\n");
		{ float res = 0.0;
		  code = gs_pop_real(minst, &res);
		  fprintf(stdout, "pop_real code = %d, result = %g\n",
			  code, res);
		  if ( code )
		    { gs_debug_dump_stack(code, NULL);
		      gs_exit(1);
		    }
		  fflush(stdout);
		}

		run_x("/Str where{pop}{/Str (ABCDE) def}ifelse\n");
		run_x("/z{//Str show}bind def\n");
		run_x("/Reps where{pop}{/Reps 1 def}ifelse Reps\n");
		code = gs_pop_integer(minst, &nreps);
		if ( code )
		  { gs_debug_dump_stack(code, NULL);
		    gs_exit(1);
		  }
		run_x("/Step where{pop}{/Step 6 def}ifelse Step\n");
		code = gs_pop_real(minst, &step);
		if ( code )
		  { gs_debug_dump_stack(code, NULL);
		    gs_exit(1);
		  }

		run_x("initmatrix (matrix ) print matrix currentmatrix == flush\n");
		run_x("usertime\n");
		for ( rep = 0; rep < nreps; rep++ )
		  {	float angle;
			for ( angle = 0; angle < 360; angle += step )
			  {	gs_initmatrix(igs);
				gs_moveto(igs, 300.0, 300.0);
				gs_rotate(igs, angle);
				run_x("z\n");
			  }
		  }
		run_x("usertime exch sub =only ( ms ) print flush\n");
		run_x("Str length Reps mul 360 Step div cvi mul =only ( chars\n) print flush\n");
		stop_x();
	  }	gs_exit(0);
/*#endif*/
	case 'Z':
		while ( *arg )
		  gs_debug[*arg++ & 127] = 0xff;
		break;
	   }
	return 0;
}

/* Define versions of strlen and strcat that encode strings in hex. */
/* This is so we can enter escaped characters regardless of whether */
/* the Level 1 convention of ignoring \s in strings-within-strings */
/* is being observed (sigh). */
private int
esc_strlen(const char *str)
{	return strlen(str) * 2 + 2;
}
private void
esc_strcat(char *dest, const char *src)
{	char *d = dest + strlen(dest);
	const char *p;
	static const char *hex = "0123456789abcdef";
	*d++ = '<';
	for ( p = src; *p; p++ )
	{	byte c = (byte)*p;
		*d++ = hex[c >> 4];
		*d++ = hex[c & 0xf];
	}
	*d++ = '>';
	*d = 0;
}

/* Process file names */
private void
argproc(gs_main_instance *minst, const char *arg)
{	runarg(minst, "", arg, ".runfile", true, true);
}
private void
runarg(gs_main_instance *minst, const char *pre, const char *arg,
  const char *post, bool init, bool flush)
{	int len = strlen(pre) + esc_strlen(arg) + strlen(post) + 1;
	char *line;
	if ( init )
	  gs_main_init2(minst);	/* Finish initialization */
	line = gs_malloc(len, 1, "argproc");
	if ( line == 0 )
	{	lprintf("Out of memory!\n");
		gs_exit(1);
	}
	strcpy(line, pre);
	esc_strcat(line, arg);
	strcat(line, post);
	run_string(minst, line, flush);
}
private void
run_string(gs_main_instance *minst, const char *str, bool flush)
{	int exit_code;
	ref error_object;
	int code = gs_main_run_string(minst, str, minst->user_errors,
				      &exit_code, &error_object);
	if ( flush || code != 0 )
	{	zflush(osp);		/* flush stdout */
		zflushpage(osp);	/* force display update */
	}
	run_finish(code, exit_code, &error_object);
}
private void
run_finish(int code, int exit_code, ref *perror_object)
{	switch ( code )
	{
	case 0:
		break;
	case e_Quit:
		gs_exit(0);
	case e_Fatal:
		eprintf1("Unrecoverable error, exit code %d\n", exit_code);
		gs_exit(exit_code);
	default:
		gs_debug_dump_stack(code, perror_object);
		gs_exit_with_code(255, code);
	}
}

/* ---------------- Print information ---------------- */

/* Print option and usage information. */
private void
print_help(gs_main_instance *minst, bool everything)
{	/* We don't support the 'everything' option yet. */
	print_revision();
	fprintf(stdout, "%s", gs_help1a);
	{	int i;
		gx_device *pdev;
		for ( i = 0; (pdev = gs_getdevice(i)) != 0; i++ )
		  fprintf(stdout, (i & 7 ? " %s" : "\n	%s"),
			  gs_devicename(pdev));
	}
	fprintf(stdout, "%s", gs_help1b);
	{	const ref *pes;
		const char *fmt = "\n\t%s";
		for ( pes = gs_emulator_name_array;
		      pes->value.const_bytes != 0; pes++
		    )
		  {	fprintf(stdout, fmt, pes->value.const_bytes);
			fmt = " %s";
		  }
	}
	fprintf(stdout, "%s", gs_help1c);
	gs_main_set_lib_paths(minst);
	{	const char **ppath = minst->lib_paths;
		while ( *ppath != 0 )
		  fprintf(stdout, "\n    %s", *ppath++);
	}
	fprintf(stdout, "%s", gs_help2a);
	fprintf(stdout, gs_help2b, gs_doc_directory);
}

/* Print the revision and revision date. */
private void
print_revision(void)
{	fprintf(stdout, "%s ", gs_product);
	print_version();
	fprintf(stdout, " (%d-%d-%d)\n%s\n",
		(int)(gs_revisiondate / 10000),
		(int)(gs_revisiondate / 100 % 100),
		(int)(gs_revisiondate % 100),
		gs_copyright);
}
private void
print_version(void)
{	fprintf(stdout, "%d.%02d",
		(int)(gs_revision / 100),
		(int)(gs_revision % 100));
}
