parse.c revision 7d8a9cc2
1/*
2 *       Copyright 1988 by Evans & Sutherland Computer Corporation,
3 *                          Salt Lake City, Utah
4 *  Portions Copyright 1989 by the Massachusetts Institute of Technology
5 *                        Cambridge, Massachusetts
6 *
7 * Copyright 1992 Claude Lecommandeur.
8 */
9
10/***********************************************************************
11 *
12 * $XConsortium: parse.c,v 1.52 91/07/12 09:59:37 dave Exp $
13 *
14 * parse the .twmrc file
15 *
16 * 17-Nov-87 Thomas E. LaStrange       File created
17 * 10-Oct-90 David M. Sternlicht       Storing saved colors on root
18 *
19 * Do the necessary modification to be integrated in ctwm.
20 * Can no longer be used for the standard twm.
21 *
22 * 22-April-92 Claude Lecommandeur.
23 *
24 ***********************************************************************/
25
26#include "ctwm.h"
27
28#include <stdio.h>
29#include <stdlib.h>
30#include <string.h>
31#include <strings.h>
32#ifdef USEM4
33# include <sys/types.h>
34# include <sys/wait.h>
35#endif
36
37#include "ctwm_atoms.h"
38#include "screen.h"
39#include "parse.h"
40#include "parse_int.h"
41#include "deftwmrc.h"
42#ifdef SOUNDS
43#  include "sound.h"
44#endif
45
46#ifndef SYSTEM_INIT_FILE
47#error "No SYSTEM_INIT_FILE set"
48#endif
49
50static bool ParseStringList(const char **sl);
51
52/*
53 * With current bison, this is defined in the gram.tab.h, so this causes
54 * a warning for redundant declaration.  With older bisons and byacc,
55 * it's not, so taking it out causes a warning for implicit declaration.
56 * A little looking around doesn't show any handy #define's we could use
57 * to be sure of the difference.  This should quiet it down on gcc/clang
58 * anyway...
59 */
60#ifdef __GNUC__
61# pragma GCC diagnostic push
62# pragma GCC diagnostic ignored "-Wredundant-decls"
63extern int yyparse(void);
64# pragma GCC diagnostic pop
65#else
66extern int yyparse(void);
67#endif
68
69// Because of how this winds up shared with callback funcs in the
70// parsing, it's difficult to unwind from being global, so just accept
71// it.
72static FILE *twmrc;
73
74static int ptr = 0;
75static int len = 0;
76#define BUF_LEN 300
77static char buff[BUF_LEN + 1];
78static const char **stringListSource, *currentString;
79
80#ifdef NON_FLEX_LEX
81/*
82 * While these are (were) referenced in a number of places through the
83 * file, overflowlen is initialized to 0, only possibly changed in
84 * twmUnput(), and unless it's non-zero, neither is otherwise touched.
85 * So this is purely a twmUnput()-related var, and with flex, never used
86 * for anything.
87 */
88static char overflowbuff[20];           /* really only need one */
89static int overflowlen;
90#endif
91
92int ConstrainedMoveTime = 400;          /* milliseconds, event times */
93bool ParseError;                        /* error parsing the .twmrc file */
94int RaiseDelay = 0;                     /* msec, for AutoRaise */
95int (*twmInputFunc)(void);              /* used in lexer */
96
97static int twmrc_lineno;
98
99
100/* Actual file loader */
101static int ParseTwmrc(const char *filename);
102
103/* lex plumbing funcs */
104static bool doparse(int (*ifunc)(void), const char *srctypename,
105                    const char *srcname);
106
107static int twmStringListInput(void);
108#ifndef USEM4
109static int twmFileInput(void);
110#else
111static int m4twmFileInput(void);
112#endif
113
114#if defined(YYDEBUG) && YYDEBUG
115int yydebug = 1;
116#endif
117
118
119/**
120 * Principal entry point from top-level code to parse the config file.
121 * This tries the various permutations of config files we could load.
122 * For most possible names, we try loading `$NAME.$SCREENNUM` before
123 * trying `$NAME`.  If a `-f filename` is given on the command line, it's
124 * passed in here, and the normal `~/.[c]twmrc*` attempts are skipped if
125 * it's not found.
126 *
127 * \param filename A filename given in the -f command-line argument (or
128 * NULL)
129 * \return true/false for whether a valid config was parsed out from
130 * somewhere.
131 */
132bool
133LoadTwmrc(const char *filename)
134{
135	int ret = -1;
136	char *tryname = NULL;
137
138	/*
139	 * Check for the twmrc file in the following order:
140	 *   0.  -f filename.#
141	 *   1.  -f filename
142	 *       (skip to 6 if -f was given)
143	 *   2.  .ctwmrc.#
144	 *   3.  .ctwmrc
145	 *   4.  .twmrc.#
146	 *   5.  .twmrc
147	 *   6.  system.ctwmrc
148	 */
149#define TRY(fn) if((ret = ParseTwmrc(fn)) != -1) { goto DONE_TRYING; }  (void)0
150
151	if(filename) {
152		/* -f filename.# */
153		asprintf(&tryname, "%s.%d", filename, Scr->screen);
154		if(tryname == NULL) {
155			// Oh, we're _screwed_...
156			return false;
157		}
158		TRY(tryname);
159
160		/* -f filename */
161		TRY(filename);
162
163		/* If we didn't get either from -f, don't try the ~ bits */
164		goto TRY_FALLBACK;
165	}
166
167	if(Home) {
168		/* ~/.ctwmrc.screennum */
169		free(tryname);
170		asprintf(&tryname, "%s/.ctwmrc.%d", Home, Scr->screen);
171		if(tryname == NULL) {
172			return false;
173		}
174		TRY(tryname);
175
176		// All later attempts are guaranteed shorter strings than that,
177		// so we can just keep sprintf'ing over it.
178
179		/* ~/.ctwmrc */
180		sprintf(tryname, "%s/.ctwmrc", Home);
181		TRY(tryname);
182
183		/* ~/.twmrc.screennum */
184		sprintf(tryname, "%s/.twmrc.%d", Home, Scr->screen);
185		TRY(tryname);
186
187		/* ~/.twmrc */
188		sprintf(tryname, "%s/.twmrc", Home);
189		TRY(tryname);
190	}
191
192TRY_FALLBACK:
193	/* system.twmrc */
194	if((ret = ParseTwmrc(SYSTEM_INIT_FILE)) != -1) {
195		if(ret && filename) {
196			// If we were -f'ing, fell back to the system default, and
197			// that succeeeded, we warn.  It's "normal"(ish) to not have
198			// a personal twmrc and fall back...
199			fprintf(stderr,
200			        "%s:  unable to open twmrc file %s, using %s instead\n",
201			        ProgramName, filename, SYSTEM_INIT_FILE);
202		}
203		goto DONE_TRYING;
204	}
205
206
207DONE_TRYING:
208#undef TRY
209	free(tryname);
210
211	/*
212	 * If we wound up with -1 all the way, we totally failed to find a
213	 * file to work with.  Fall back to builtin config.
214	 */
215	if(ret == -1) {
216		// Only warn if -f.
217		if(filename) {
218			fprintf(stderr,
219			        "%s:  unable to open twmrc file %s, using built-in defaults instead\n",
220			        ProgramName, filename);
221		}
222		return ParseStringList(defTwmrc);
223	}
224
225
226	/* Better have a useful value in ret... */
227	return ret;
228}
229
230
231/**
232 * Try parsing a file as a ctwmrc.
233 *
234 * \param filename The filename to try opening and parsing.
235 * \return -1,0,1.  0/1 should be treated as false/true for whether
236 * parsing the file succeeded.  -1 means the file couldn't be opened.
237 */
238static int
239ParseTwmrc(const char *filename)
240{
241	bool status;
242
243#if 0
244	fprintf(stderr, "%s(): Trying %s\n", __func__, filename);
245#endif
246
247	/* See if we can open the file */
248	if(!filename) {
249		return -1;
250	}
251	twmrc = fopen(filename, "r");
252	if(!twmrc) {
253		return -1;
254	}
255
256
257	/* Got it.  Kick off the parsing, however we do it. */
258#ifdef USEM4
259	FILE *raw = NULL;
260	if(CLarg.GoThroughM4) {
261		/*
262		 * Hold onto raw filehandle so we can fclose() it below, and
263		 * swap twmrc over to the output from m4
264		 */
265		raw = twmrc;
266		twmrc = start_m4(raw);
267	}
268	status = doparse(m4twmFileInput, "file", filename);
269	wait(0);
270	fclose(twmrc);
271	if(raw) {
272		fclose(raw);
273	}
274#else
275	status = doparse(twmFileInput, "file", filename);
276	fclose(twmrc);
277#endif
278
279	/* And we're done */
280	return status;
281
282	/* NOTREACHED */
283}
284
285static bool
286ParseStringList(const char **sl)
287{
288	stringListSource = sl;
289	currentString = *sl;
290	return doparse(twmStringListInput, "string list", NULL);
291}
292
293
294/*
295 * Util used throughout the code (possibly often wrongly?)
296 */
297void twmrc_error_prefix(void)
298{
299	fprintf(stderr, "%s:  line %d:  ", ProgramName, twmrc_lineno);
300}
301
302
303
304/*
305 * Everything below here is related to plumbing and firing off lex/yacc
306 */
307
308
309/*
310 * Backend func that takes an input-providing func and hooks it up to the
311 * lex/yacc parser to do the work
312 */
313static bool
314doparse(int (*ifunc)(void), const char *srctypename,
315        const char *srcname)
316{
317	ptr = 0;
318	len = 0;
319	twmrc_lineno = 0;
320	ParseError = false;
321	twmInputFunc = ifunc;
322#ifdef NON_FLEX_LEX
323	overflowlen = 0;
324#endif
325
326	yyparse();
327
328	if(ParseError) {
329		fprintf(stderr, "%s:  errors found in twm %s",
330		        ProgramName, srctypename);
331		if(srcname) {
332			fprintf(stderr, " \"%s\"", srcname);
333		}
334		fprintf(stderr, "\n");
335	}
336	return !(ParseError);
337}
338
339
340/*
341 * Various input routines for the lexer for the various sources of
342 * config.
343 */
344
345#ifndef USEM4
346#include <ctype.h>
347
348/* This has Tom's include() funtionality.  This is utterly useless if you
349 * can use m4 for the same thing.               Chris P. Ross */
350
351#define MAX_INCLUDES 10
352
353static struct incl {
354	FILE *fp;
355	char *name;
356	int lineno;
357} rc_includes[MAX_INCLUDES];
358static int include_file = 0;
359
360
361static int twmFileInput(void)
362{
363#ifdef NON_FLEX_LEX
364	if(overflowlen) {
365		return (int) overflowbuff[--overflowlen];
366	}
367#endif
368
369	while(ptr == len) {
370		while(include_file) {
371			if(fgets(buff, BUF_LEN, rc_includes[include_file].fp) == NULL) {
372				free(rc_includes[include_file].name);
373				fclose(rc_includes[include_file].fp);
374				twmrc_lineno = rc_includes[include_file--].lineno;
375			}
376			else {
377				break;
378			}
379		}
380
381		if(!include_file)
382			if(fgets(buff, BUF_LEN, twmrc) == NULL) {
383				return 0;
384			}
385		twmrc_lineno++;
386
387		if(strncmp(buff, "include", 7) == 0) {
388			/* Whoops, an include file! */
389			char *p = buff + 7, *q;
390			FILE *fp;
391
392			while(isspace(*p)) {
393				p++;
394			}
395			for(q = p; *q && !isspace(*q); q++) {
396				continue;
397			}
398			*q = 0;
399
400			if((fp = fopen(p, "r")) == NULL) {
401				fprintf(stderr, "%s: Unable to open included init file %s\n",
402				        ProgramName, p);
403				continue;
404			}
405			if(++include_file >= MAX_INCLUDES) {
406				fprintf(stderr, "%s: init file includes nested too deep\n",
407				        ProgramName);
408				continue;
409			}
410			rc_includes[include_file].fp = fp;
411			rc_includes[include_file].lineno = twmrc_lineno;
412			twmrc_lineno = 0;
413			rc_includes[include_file].name = strdup(p);
414			continue;
415		}
416		ptr = 0;
417		len = strlen(buff);
418	}
419	return ((int)buff[ptr++]);
420}
421#else /* USEM4 */
422/* If you're going to use m4, use this version instead.  Much simpler.
423 * m4 ism's credit to Josh Osborne (stripes) */
424
425static int m4twmFileInput(void)
426{
427	int line;
428	static FILE *cp = NULL;
429
430	if(cp == NULL && CLarg.keepM4_filename) {
431		cp = fopen(CLarg.keepM4_filename, "w");
432		if(cp == NULL) {
433			fprintf(stderr,
434			        "%s:  unable to create m4 output %s, ignoring\n",
435			        ProgramName, CLarg.keepM4_filename);
436			CLarg.keepM4_filename = NULL;
437		}
438	}
439
440#ifdef NON_FLEX_LEX
441	if(overflowlen) {
442		return((int) overflowbuff[--overflowlen]);
443	}
444#endif
445
446	while(ptr == len) {
447nextline:
448		if(fgets(buff, BUF_LEN, twmrc) == NULL) {
449			if(cp) {
450				fclose(cp);
451			}
452			return(0);
453		}
454		if(cp) {
455			fputs(buff, cp);
456		}
457
458		if(sscanf(buff, "#line %d", &line)) {
459			twmrc_lineno = line - 1;
460			goto nextline;
461		}
462		else {
463			twmrc_lineno++;
464		}
465
466		ptr = 0;
467		len = strlen(buff);
468	}
469	return ((int)buff[ptr++]);
470}
471#endif /* USEM4 */
472
473
474static int twmStringListInput(void)
475{
476#ifdef NON_FLEX_LEX
477	if(overflowlen) {
478		return (int) overflowbuff[--overflowlen];
479	}
480#endif
481
482	/*
483	 * return the character currently pointed to
484	 */
485	if(currentString) {
486		unsigned int c = (unsigned int) * currentString++;
487
488		if(c) {
489			return c;        /* if non-nul char */
490		}
491		currentString = *++stringListSource;  /* advance to next bol */
492		return '\n';                    /* but say that we hit last eol */
493	}
494	return 0;                           /* eof */
495}
496
497
498
499/*
500 * unput/output funcs for AT&T lex.  No longer supported, and expected to
501 * be GC'd in a release or two.
502 */
503#ifdef NON_FLEX_LEX
504
505void twmUnput(int c)
506{
507	if(overflowlen < sizeof overflowbuff) {
508		overflowbuff[overflowlen++] = (char) c;
509	}
510	else {
511		twmrc_error_prefix();
512		fprintf(stderr, "unable to unput character (%c)\n",
513		        c);
514	}
515}
516
517void TwmOutput(int c)
518{
519	putchar(c);
520}
521
522#endif /* NON_FLEX_LEX */
523