xref: /plan9/sys/src/cmd/gs/src/geninit.c (revision 593dc095aefb2a85c828727bbfa9da139a49bdf4)
1 /* Copyright (C) 1995, 2000 Aladdin Enterprises.  All rights reserved.
2 
3   This software is provided AS-IS with no warranty, either express or
4   implied.
5 
6   This software is distributed under license and may not be copied,
7   modified or distributed except as expressly authorized under the terms
8   of the license contained in the file LICENSE in this distribution.
9 
10   For more information about licensing, please refer to
11   http://www.ghostscript.com/licensing/. For information on
12   commercial licensing, go to http://www.artifex.com/licensing/ or
13   contact Artifex Software, Inc., 101 Lucas Valley Road #110,
14   San Rafael, CA  94903, U.S.A., +1(415)492-9861.
15 */
16 
17 /* $Id: geninit.c,v 1.8 2003/07/01 04:37:20 alexcher Exp $ */
18 /*
19  * Utility for merging all the Ghostscript initialization files (gs_*.ps)
20  * into a single file, optionally converting them to C data.  Usage:
21  *    geninit [-(I|i) <prefix>] <init-file.ps> <gconfig.h> <merged-init-file.ps>
22  *    geninit [-(I|i) <prefix>] <init-file.ps> <gconfig.h> -c <merged-init-file.c>
23  *
24  * The following special constructs are recognized in the input files:
25  *	%% Replace[%| ]<#lines> (<psfile>)
26  *	%% Replace[%| ]<#lines> INITFILES
27  * '%' after Replace means that the file to be included intact.
28  * If the first non-comment, non-blank line in a file ends with the
29  * LanguageLevel 2 constructs << or <~, its section of the merged file
30  * will begin with
31  *	currentobjectformat 1 setobjectformat
32  * and end with
33  *	setobjectformat
34  * and if any line then ends with with <, the following ASCIIHex string
35  * will be converted to a binary token.
36  */
37 #include "stdpre.h"
38 #include <ctype.h>
39 #include <stdio.h>
40 #include <stdlib.h>		/* for exit() */
41 #include <string.h>
42 #include <memory.h>
43 
44 /* Forward references */
45 private FILE *prefix_open(const char *prefix, const char *inname);
46 private void merge_to_c(const char *prefix, const char *inname, FILE * in,
47 			FILE * config, FILE * out);
48 private void merge_to_ps(const char *prefix, const char *inname, FILE * in,
49 			 FILE * config, FILE * out);
50 
51 #define LINE_SIZE 128
52 
53 int
main(int argc,char * argv[])54 main(int argc, char *argv[])
55 {
56     int arg_c = argc;
57     char **arg_v = argv;
58     const char *fin;
59     FILE *in;
60     const char *fconfig;
61     FILE *config;
62     const char *fout;
63     FILE *out;
64     const char *prefix = "";
65     bool to_c = false;
66 
67     if (arg_c >= 2 && (!strcmp(arg_v[1], "-I") || !strcmp(arg_v[1], "-i"))) {
68 	prefix = arg_v[2];
69 	arg_c -= 2;
70 	arg_v += 2;
71     }
72     if (arg_c == 4)
73 	fin = arg_v[1], fconfig = arg_v[2], fout = arg_v[3];
74     else if (arg_c == 5 && !strcmp(arg_v[3], "-c"))
75 	fin = arg_v[1], fconfig = arg_v[2], fout = arg_v[4], to_c = true;
76     else {
77 	fprintf(stderr, "\
78 Usage: geninit [-(I|i) lib/] gs_init.ps gconfig.h gs_xinit.ps\n\
79 or     geninit [-(I|i) lib/] gs_init.ps gconfig.h -c gs_init.c\n");
80 	exit(1);
81     }
82     in = prefix_open(prefix, fin);
83     if (in == 0)
84 	exit(1);
85     config = fopen(fconfig, "r");
86     if (config == 0) {
87 	fprintf(stderr, "Cannot open %s for reading.\n", fconfig);
88 	fclose(in);
89 	exit(1);
90     }
91     out = fopen(fout, "w");
92     if (out == 0) {
93 	fprintf(stderr, "Cannot open %s for writing.\n", fout);
94 	fclose(config);
95 	fclose(in);
96 	exit(1);
97     }
98     if (to_c)
99 	merge_to_c(prefix, fin, in, config, out);
100     else
101 	merge_to_ps(prefix, fin, in, config, out);
102     fclose(out);
103     return 0;
104 }
105 
106 /* Translate a path from Postscript notation to platform notation. */
107 
108 private void
translate_path(char * path)109 translate_path(char *path)
110 {
111     /*
112      * It looks that we only need to do for Mac OS.
113      * We don't support 16-bit DOS/Windows, which wants '\'.
114      * Win32 API supports '/'.
115      */
116 #ifdef __MACOS__
117     if (path[0] == '.' && path[1] == '.' && path[2] == '/') {
118 	char *p;
119 
120 	path[0] = path[1] = ':';
121 	for (p = path + 2; p[1] ; p++)
122 	    *p = (p[1] == '/' ? ':' : p[1]);
123 	*p = 0;
124     }
125 #endif
126 }
127 
128 /* Open a file with a name prefix. */
129 private FILE *
prefix_open(const char * prefix,const char * inname)130 prefix_open(const char *prefix, const char *inname)
131 {
132     char fname[LINE_SIZE + 1];
133     int prefix_length = strlen(prefix);
134     FILE *f;
135 
136     if (prefix_length + strlen(inname) > LINE_SIZE) {
137 	fprintf(stderr, "File name > %d characters, too long.\n",
138 		LINE_SIZE);
139 	exit(1);
140     }
141     strcpy(fname, prefix);
142     strcat(fname, inname);
143     translate_path(fname + prefix_length);
144     f = fopen(fname, "r");
145     if (f == NULL)
146 	fprintf(stderr, "Cannot open %s for reading.\n", fname);
147     return f;
148 }
149 
150 /* Read a line from the input. */
151 private bool
rl(FILE * in,char * str,int len)152 rl(FILE * in, char *str, int len)
153 {
154     /*
155      * Unfortunately, we can't use fgets here, because the typical
156      * implementation only recognizes the EOL convention of the current
157      * platform.
158      */
159     int i = 0, c = getc(in);
160 
161     if (c < 0)
162 	return false;
163     while (i < len - 1) {
164 	if (c < 0 || c == 10)		/* ^J, Unix EOL */
165 	    break;
166 	if (c == 13) {		/* ^M, Mac EOL */
167 	    c = getc(in);
168 	    if (c != 10 && c >= 0)	/* ^M^J, PC EOL */
169 		ungetc(c, in);
170 	    break;
171 	}
172 	str[i++] = c;
173 	c = getc(in);
174     }
175     str[i] = 0;
176     return true;
177 }
178 
179 /* Write a string or a line on the output. */
180 private void
wsc(FILE * out,const byte * str,int len)181 wsc(FILE * out, const byte *str, int len)
182 {
183     int n = 0;
184     const byte *p = str;
185     int i;
186 
187     for (i = 0; i < len; ++i) {
188 	int c = str[i];
189 
190 	fprintf(out,
191 		(c < 32 || c >= 127 ? "%d," :
192 		 c == '\'' || c == '\\' ? "'\\%c'," : "'%c',"),
193 		c);
194 	if (++n == 15) {
195 	    fputs("\n", out);
196 	    n = 0;
197 	}
198     }
199     if (n != 0)
200 	fputc('\n', out);
201 }
202 private void
ws(FILE * out,const byte * str,int len,bool to_c)203 ws(FILE * out, const byte *str, int len, bool to_c)
204 {
205     if (to_c)
206 	wsc(out, str, len);
207     else
208 	fwrite(str, 1, len, out);
209 }
210 private void
wl(FILE * out,const char * str,bool to_c)211 wl(FILE * out, const char *str, bool to_c)
212 {
213     ws(out, (const byte *)str, strlen(str), to_c);
214     ws(out, (const byte *)"\n", 1, to_c);
215 }
216 
217 /*
218  * Strip whitespace and comments from a line of PostScript code as possible.
219  * Return a pointer to any string that remains, or NULL if none.
220  * Note that this may store into the string.
221  */
222 private char *
doit(char * line,bool intact)223 doit(char *line, bool intact)
224 {
225     char *str = line;
226     char *from;
227     char *to;
228     int in_string = 0;
229 
230     if (intact)
231 	return str;
232     while (*str == ' ' || *str == '\t')		/* strip leading whitespace */
233 	++str;
234     if (*str == 0)		/* all whitespace */
235 	return NULL;
236     if (!strncmp(str, "%END", 4))	/* keep these for .skipeof */
237 	return str;
238     if (str[0] == '%')    /* comment line */
239 	return NULL;
240     /*
241      * Copy the string over itself removing:
242      *  - All comments not within string literals;
243      *  - Whitespace adjacent to '[' ']' '{' '}';
244      *  - Whitespace before '/' '(' '<';
245      *  - Whitespace after ')' '>'.
246      */
247     for (to = from = str; (*to = *from) != 0; ++from, ++to) {
248 	switch (*from) {
249 	    case '%':
250 		if (!in_string)
251 		    break;
252 		continue;
253 	    case ' ':
254 	    case '\t':
255 		if (to > str && !in_string && strchr(" \t>[]{})", to[-1]))
256 		    --to;
257 		continue;
258 	    case '(':
259 	    case '<':
260 	    case '/':
261 	    case '[':
262 	    case ']':
263 	    case '{':
264 	    case '}':
265 		if (to > str && !in_string && strchr(" \t", to[-1]))
266 		    *--to = *from;
267                 if (*from == '(')
268                     ++in_string;
269               	continue;
270 	    case ')':
271 		--in_string;
272 		continue;
273 	    case '\\':
274 		if (from[1] == '\\' || from[1] == '(' || from[1] == ')')
275 		    *++to = *++from;
276 		continue;
277 	    default:
278 		continue;
279 	}
280 	break;
281     }
282     /* Strip trailing whitespace. */
283     while (to > str && (to[-1] == ' ' || to[-1] == '\t'))
284 	--to;
285     *to = 0;
286     return str;
287 }
288 
289 /* Copy a hex string to the output as a binary token. */
290 private void
hex_string_to_binary(FILE * out,FILE * in,bool to_c)291 hex_string_to_binary(FILE *out, FILE *in, bool to_c)
292 {
293 #define MAX_STR 0xffff	/* longest possible PostScript string token */
294     byte *strbuf = (byte *)malloc(MAX_STR);
295     byte *q = strbuf;
296     int c, digit;
297     bool which = false;
298     int len;
299     byte prefix[3];
300 
301     if (strbuf == 0) {
302 	fprintf(stderr, "Unable to allocate string token buffer.\n");
303 	exit(1);
304     }
305     while ((c = getc(in)) >= 0) {
306 	if (isxdigit(c)) {
307 	    c -= (isdigit(c) ? '0' : islower(c) ? 'a' : 'A');
308 	    if ((which = !which)) {
309 		if (q == strbuf + MAX_STR) {
310 		    fprintf(stderr, "Can't handle string token > %d bytes.\n",
311 			    MAX_STR);
312 		    exit(1);
313 		}
314 		*q++ = c << 4;
315 	    } else
316 		q[-1] += c;
317 	} else if (isspace(c))
318 	    continue;
319 	else if (c == '>')
320 	    break;
321 	else
322 	    fprintf(stderr, "Unknown character in ASCIIHex string: %c\n", c);
323     }
324     len = q - strbuf;
325     if (len <= 255) {
326 	prefix[0] = 142;
327 	prefix[1] = (byte)len;
328 	ws(out, prefix, 2, to_c);
329     } else {
330 	prefix[0] = 143;
331 	prefix[1] = (byte)(len >> 8);
332 	prefix[2] = (byte)len;
333 	ws(out, prefix, 3, to_c);
334     }
335     ws(out, strbuf, len, to_c);
336     free((char *)strbuf);
337 #undef MAX_STR
338 }
339 
340 /* Merge a file from input to output. */
341 private void
flush_buf(FILE * out,char * buf,bool to_c)342 flush_buf(FILE * out, char *buf, bool to_c)
343 {
344     if (buf[0] != 0) {
345 	wl(out, buf, to_c);
346 	buf[0] = 0;
347     }
348 }
349 private void
mergefile(const char * prefix,const char * inname,FILE * in,FILE * config,FILE * out,bool to_c,bool intact)350 mergefile(const char *prefix, const char *inname, FILE * in, FILE * config,
351 	  FILE * out, bool to_c, bool intact)
352 {
353     char line[LINE_SIZE + 1];
354     char buf[LINE_SIZE + 1];
355     char *str;
356     int level = 1;
357     bool first = true;
358 
359     buf[0] = 0;
360     while (rl(in, line, LINE_SIZE)) {
361 	char psname[LINE_SIZE + 1];
362 	int nlines;
363 
364 	if (!strncmp(line, "%% Replace", 10) &&
365 	    sscanf(line + 11, "%d %s", &nlines, psname) == 2
366 	    ) {
367 	    bool do_intact = (line[10] == '%');
368 
369 	    flush_buf(out, buf, to_c);
370 	    while (nlines-- > 0)
371 		rl(in, line, LINE_SIZE);
372 	    if (psname[0] == '(') {
373 		FILE *ps;
374 
375 		psname[strlen(psname) - 1] = 0;
376 		ps = prefix_open(prefix, psname + 1);
377 		if (ps == 0)
378 		    exit(1);
379 		mergefile(prefix, psname + 1, ps, config, out, to_c, intact || do_intact);
380 	    } else if (!strcmp(psname, "INITFILES")) {
381 		/*
382 		 * We don't want to bind config.h into geninit, so
383 		 * we parse it ourselves at execution time instead.
384 		 */
385 		rewind(config);
386 		while (rl(config, psname, LINE_SIZE))
387 		    if (!strncmp(psname, "psfile_(\"", 9)) {
388 			FILE *ps;
389 			char *quote = strchr(psname + 9, '"');
390 
391 			*quote = 0;
392 			ps = prefix_open(prefix, psname + 9);
393 			if (ps == 0)
394 			    exit(1);
395 			mergefile(prefix, psname + 9, ps, config, out, to_c, false);
396 		    }
397 	    } else {
398 		fprintf(stderr, "Unknown %%%% Replace %d %s\n",
399 			nlines, psname);
400 		exit(1);
401 	    }
402 	} else if (!strcmp(line, "currentfile closefile")) {
403 	    /* The rest of the file is debugging code, stop here. */
404 	    break;
405 	} else {
406 	    int len;
407 
408 	    str = doit(line, intact);
409 	    if (str == 0)
410 		continue;
411 	    len = strlen(str);
412 	    if (first && len >= 2 && str[len - 2] == '<' &&
413 		(str[len - 1] == '<' || str[len - 1] == '~')
414 		) {
415 		wl(out, "currentobjectformat 1 setobjectformat\n", to_c);
416 		level = 2;
417 	    }
418 	    if (level > 1 && len > 0 && str[len - 1] == '<' &&
419 		(len < 2 || str[len - 2] != '<')
420 		) {
421 		/* Convert a hex string to a binary token. */
422 		flush_buf(out, buf, to_c);
423 		str[len - 1] = 0;
424 		wl(out, str, to_c);
425 		hex_string_to_binary(out, in, to_c);
426 		continue;
427 	    }
428 	    if (buf[0] != '%' &&	/* i.e. not special retained comment */
429 		strlen(buf) + len < LINE_SIZE &&
430 		(strchr("(/[]{}", str[0]) ||
431 		 (buf[0] != 0 && strchr(")[]{}", buf[strlen(buf) - 1])))
432 		)
433 		strcat(buf, str);
434 	    else {
435 		flush_buf(out, buf, to_c);
436 		strcpy(buf, str);
437 	    }
438 	    first = false;
439 	}
440     }
441     flush_buf(out, buf, to_c);
442     if (level > 1)
443 	wl(out, "setobjectformat", to_c);
444     fprintf(stderr, "%s: %ld bytes, output pos = %ld\n",
445 	    inname, ftell(in), ftell(out));
446     fclose(in);
447 }
448 
449 /* Merge and produce a C file. */
450 private void
merge_to_c(const char * prefix,const char * inname,FILE * in,FILE * config,FILE * out)451 merge_to_c(const char *prefix, const char *inname, FILE * in, FILE * config,
452 	   FILE * out)
453 {
454     char line[LINE_SIZE + 1];
455 
456     fputs("/*\n", out);
457     while ((rl(in, line, LINE_SIZE), line[0]))
458 	fprintf(out, "%s\n", line);
459     fputs("*/\n", out);
460     fputs("\n", out);
461     fputs("/* Pre-compiled interpreter initialization string. */\n", out);
462     fputs("\n", out);
463     fputs("const unsigned char gs_init_string[] = {\n", out);
464     mergefile(prefix, inname, in, config, out, true, false);
465     fputs("10};\n", out);
466     fputs("const unsigned int gs_init_string_sizeof = sizeof(gs_init_string);\n", out);
467 }
468 
469 /* Merge and produce a PostScript file. */
470 private void
merge_to_ps(const char * prefix,const char * inname,FILE * in,FILE * config,FILE * out)471 merge_to_ps(const char *prefix, const char *inname, FILE * in, FILE * config,
472 	    FILE * out)
473 {
474     char line[LINE_SIZE + 1];
475 
476     while ((rl(in, line, LINE_SIZE), line[0]))
477 	fprintf(out, "%s\n", line);
478     mergefile(prefix, inname, in, config, out, false, false);
479 }
480