1 /************************************************************************
2  * This program is Copyright (C) 1986-1996 by Jonathan Payne.  JOVE is  *
3  * provided to you without charge, and with no warranty.  You may give  *
4  * away copies of JOVE, including sources, provided that this notice is *
5  * included in all the files.                                           *
6  ************************************************************************/
7 
8 #include "jove.h"
9 #include "fp.h"
10 #include "jctype.h"
11 #include "chars.h"
12 #include "commands.h"
13 #include "disp.h"
14 #include "re.h"
15 #include "ask.h"
16 #include "extend.h"
17 #include "fmt.h"
18 #include "insert.h"
19 #include "move.h"
20 #include "sysprocs.h"
21 #include "proc.h"
22 /* #include "util.h" */
23 #include "vars.h"
24 
25 #ifdef SUBSHELL
26 # include <errno.h>
27 #endif
28 
29 #ifdef MAC
30 # include "mac.h"
31 #endif
32 
33 #ifdef MSDOS_PROCS
34 # include <process.h>
35 #endif
36 
37 private void
38 	DefAutoExec proto((data_obj *(*proc) ptrproto((const char *))));
39 
40 int	InJoverc = 0;
41 
42 /* Auto execute code */
43 
44 #define NEXECS	20
45 
46 private struct AutoExec {
47 	char	*a_pattern;
48 	data_obj	*a_cmd;
49 	int	a_arg_state,
50 		a_arg_count;
51 } AutoExecs[NEXECS];	/* must be initialized by system to 0 */
52 
53 private int	ExecIndex = 0;
54 
55 /* Command auto-execute. */
56 
57 void
CAutoExec()58 CAutoExec()
59 {
60 	DefAutoExec(findcom);
61 }
62 
63 /* Macro auto-execute. */
64 
65 void
MAutoExec()66 MAutoExec()
67 {
68 	DefAutoExec(findmac);
69 }
70 
71 private void
72 DefAutoExec(proc)
73 data_obj	*(*proc) ptrproto((const char *));
74 {
75 	data_obj	*d = (*proc)(ProcFmt);
76 	char	*pattern;
77 	register struct AutoExec	*p;
78 
79 	pattern = do_ask("\r\n", NULL_ASK_EXT, (char *) NULL, ": %f %s ", d->Name);
80 	for (p = AutoExecs; p != &AutoExecs[ExecIndex]; p++)
81 		if (p->a_cmd == d
82 		&& ((pattern == NULL || p->a_pattern == NULL)?
83 			(pattern == p->a_pattern) : (strcmp(pattern, p->a_pattern) == 0))
84 		&& p->a_arg_state == arg_state
85 		&& p->a_arg_count == arg_count)
86 			return;		/* eliminate duplicates */
87 	if (ExecIndex >= NEXECS)
88 		complain("Too many auto-executes, max %d.", NEXECS);
89 	p->a_pattern = copystr(pattern);
90 	p->a_cmd = d;
91 	p->a_arg_state = arg_state;
92 	p->a_arg_count = arg_count;
93 	ExecIndex += 1;
94 }
95 
96 /* DoAutoExec: NEW and OLD are file names, and if NEW and OLD aren't the
97    same kind of file (i.e., match the same pattern) or OLD is NULL and it
98    matches, OR if the pattern is NULL (none was specified) then, we execute
99    the command associated with that kind of file. */
100 
101 void
DoAutoExec(new,old)102 DoAutoExec(new, old)
103 register char	*new,
104 		*old;
105 {
106 	register struct AutoExec	*p;
107 
108 	for (p = AutoExecs; p != &AutoExecs[ExecIndex]; p++) {
109 		if (p->a_pattern == NULL
110 		|| ((new != NULL && LookingAt(p->a_pattern, new, 0))
111 		   && !(old != NULL && LookingAt(p->a_pattern, old, 0))))
112 		{
113 			arg_state = p->a_arg_state;
114 			arg_count = p->a_arg_count;
115 			ExecCmd(p->a_cmd);
116 			clr_arg_value();
117 		}
118 	}
119 }
120 
121 ZXchar
addgetc()122 addgetc()	/* NOTE: can return EOF */
123 {
124 	ZXchar	c;
125 
126 	if (!InJoverc) {
127 		Asking = YES;
128 		AskingWidth = strlen(mesgbuf);
129 		c = getch();
130 		Asking = NO;
131 		add_mess("%p ", c);
132 	} else {
133 		c = getch();
134 		switch (c) {
135 		case '\n':
136 			c = EOF;	/* this isn't part of the sequence */
137 			break;
138 		case '\\':
139 		case '^':
140 			c = DecodePair(c, getch());
141 			break;
142 		}
143 	}
144 	return c;
145 }
146 
147 void
Extend()148 Extend()
149 {
150 	ExecCmd(findcom(": "));
151 }
152 
153 /* Read a positive integer from CP.  It must be in base BASE, and
154    complains if it isn't.  If allints, all the characters
155    in the string must be integers or we return NO (failure); otherwise
156    we stop reading at the first nondigit and return YES (success). */
157 
158 bool
chr_to_int(cp,base,allints,result)159 chr_to_int(cp, base, allints, result)
160 register char	*cp;
161 int	base;
162 bool	allints;
163 register int	*result;
164 {
165 	register char	c;
166 	int	value = 0,
167 		sign;
168 
169 	if ((c = *cp) == '-') {
170 		sign = -1;
171 		cp += 1;
172 	} else
173 		sign = 1;
174 	while ((c = *cp++) != '\0') {
175 		if (!jisdigit(c)) {
176 			if (allints)
177 				return NO;
178 			break;
179 		}
180 		c = c - '0';
181 		if (c >= base)
182 			complain("You must specify in base %d.", base);
183 		value = value * base + c;
184 	}
185 	*result = value * sign;
186 	return YES;
187 }
188 
189 int
ask_int(def,prompt,base)190 ask_int(def, prompt, base)
191 char	*def;
192 char	*prompt;
193 int	base;
194 {
195 	char	*val = ask(def, prompt);
196 	int	value;
197 
198 	if (!chr_to_int(val, base, YES, &value))
199 		complain("That's not a number!");
200 	return value;
201 }
202 
203 void
vpr_aux(vp,buf,size)204 vpr_aux(vp, buf, size)
205 register const struct variable	*vp;
206 char	*buf;
207 size_t	size;
208 {
209 	switch (vp->v_flags & V_TYPEMASK) {
210 	case V_INT:
211 	case V_WHOLEX:
212 	case V_WHOLE:
213 	case V_NAT:
214 		swritef(buf, size, (vp->v_flags & V_FMODE)? "%03o" : "%d",
215 			*((int *) vp->v_value));
216 		break;
217 
218 	case V_BOOL:
219 		swritef(buf, size, (*((int *) vp->v_value)) ? "on" : "off");
220 		break;
221 
222 	case V_STRING:
223 	case V_FILENAME:
224 		swritef(buf, size, "%s", vp->v_value);
225 		break;
226 
227 	case V_CHAR:
228 		swritef(buf, size, "%p", *((ZXchar *) vp->v_value));
229 		break;
230 	}
231 }
232 
233 void
PrVar()234 PrVar()
235 {
236 	struct variable	*vp = (struct variable *) findvar(ProcFmt);
237 	char	prbuf[MAXCOLS];
238 
239 	vpr_aux(vp, prbuf, sizeof(prbuf));
240 	f_mess(": %f %s => %s", vp->Name, prbuf);
241 	stickymsg = YES;
242 }
243 
244 void
vset_aux(vp,prompt)245 vset_aux(vp, prompt)
246 const struct variable	*vp;
247 char	*prompt;
248 {
249 	switch (vp->v_flags & V_TYPEMASK) {
250 	case V_INT:
251 	case V_WHOLEX:
252 	case V_WHOLE:
253 	case V_NAT:
254 	    {
255 		char	def[30];
256 		static const int	lwbt[] = {
257 			~0,	/* V_INT -- I hope we are two's complement */
258 			-1,	/* V_WHOLEX */
259 			0,	/* V_WHOLE */
260 			1,	/* V_NAT */
261 			};
262 		int
263 			val,
264 			lwb = lwbt[(vp->v_flags & V_TYPEMASK) - V_INT];
265 
266 		vpr_aux(vp, def, sizeof(def));
267 		val = ask_int(def, prompt, (vp->v_flags & V_FMODE)? 8 : 10);
268 		if (val < lwb)
269 			complain("[%s must not be less than %d]", vp->Name, lwb);
270 		*((int *) vp->v_value) = val;
271 		break;
272 	    }
273 
274 	case V_BOOL:
275 	    {
276 		static char	*possible[/*bool*/] = {"off", "on", NULL };
277 		bool	*valp = (bool *) vp->v_value;
278 		int	newval = complete(possible, possible[!*valp], prompt,
279 			CASEIND | ALLOW_OLD | ALLOW_EMPTY);
280 
281 		if (newval < 0)
282 			newval = !*valp;
283 		*valp = newval;
284 #ifdef MAC
285 		MarkVar(vp, -1, 0);	/* mark the menu item */
286 #endif
287 		s_mess("%s%s", prompt, possible[newval]);
288 		break;
289 	    }
290 
291 	case V_FILENAME:
292 	    {
293 		char	fbuf[FILESIZE];
294 
295 		strcpy((char *) vp->v_value,
296 			ask_file(prompt, (char *) vp->v_value, fbuf));
297 		break;
298 	    }
299 
300 	case V_STRING:
301 	    {
302 		char	*str;
303 
304 		/* Do_ask() so you can set string to "" if you so desire. */
305 		str = do_ask("\r\n", NULL_ASK_EXT, (char *) vp->v_value, prompt);
306 		if (str == NULL)
307 			str = NullStr;
308 		if (strlen(str) >= vp->v_size)
309 			complain("string too long");
310 		strcpy((char *) vp->v_value, str);
311 		break;
312 	    }
313 
314 	case V_CHAR:
315 		s_mess(prompt);
316 		*((ZXchar *) vp->v_value) = addgetc();
317 		break;
318 	}
319 
320 	if (vp->v_flags & V_MODELINE)
321 		UpdModLine = YES;
322 	if (vp->v_flags & V_CLRSCREEN)
323 		ClAndRedraw();
324 	if (vp->v_flags & V_TTY_RESET)
325 		tty_adjust();
326 #ifdef UNIX
327 	if (vp->v_flags & V_UPDFREQ)
328 		SetClockAlarm(YES);
329 #endif
330 #if defined(USE_CTYPE) && !defined(NO_SETLOCALE)
331 	if (vp->v_flags & V_LOCALE) {
332 		locale_adjust();
333 		ClAndRedraw();
334 	}
335 #endif
336 }
337 
338 void
SetVar()339 SetVar()
340 {
341 	struct variable	*vp = (struct variable *) findvar(ProcFmt);
342 	char	prompt[128];
343 
344 	swritef(prompt, sizeof(prompt), ": %f %s ", vp->Name);
345 	vset_aux(vp, prompt);
346 }
347 
348 /* complete: buffer/command/keymap/macro/variable name completion.
349  *
350  * possible: an array of strings
351  * prompt: the prompt to use
352  * flags: a set of flags:
353  *   CASEIND: ignore case
354  *   ALLOW_OLD: allow answer listed in possible
355  *   ALLOW_NEW: allow answer not listed in possible
356  *   ALLOW_EMPTY: allow empty answer
357  *
358  * complete returns an index into possible, or -1 if there is no
359  * right answer there, in which case what the user typed is in Minibuf.
360  *
361  * aux_complete is called by real_ask, on behalf of complete, to handle:
362  *
363  * ?        Typeout possible completions (does not change the answer)
364  *
365  * TAB      Extend answer as much as possible (this might actually
366  *          involve shrinking the answer until it is the prefix of
367  *          at least one Possible; if so, error).
368  *
369  * SP       If answer is complete (i.e. matches a whole entry in Possible),
370  *          accept it.  Otherwise, extend answer as much as possible;
371  *          if the result is unique, and we didn't shrink the answer,
372  *          accept it.
373  *
374  * CR or NL If ALLOW_NEW and answer non-empty: accept the answer.
375  *          If ALLOW_EMPTY and answer empty: accept the answer.
376  *          If ALLOW_OLD and answer is complete: accept it.
377  *          If ALLOW_OLD and answer is prefix of a unique Possible: accept that Possible.
378  *          If ALLOW_INDEX and answer is a numeral that is a legitimate
379  *          index into possible, accept as that possible.
380  *          Otherwise: error
381  *
382  * If we are InJoverc, we cannot be interactive, so ? is forbidden
383  * and all the others are treated as CR; an error is fatal.
384  *
385  * The following file-static variables smuggle values from complete
386  * to aux_complete and vice versa, behind the back of do_ask etc.
387  * This could be nicer if C supported nested procedures (closures);
388  * perhaps we should simulate them.
389  */
390 
391 private char	**Possible;	/* possible arg of complete */
392 private int
393 	comp_flags,	/* flags arg of complete */
394 	comp_value;	/* return value for complete; set by aux_complete */
395 
396 private bool aux_complete proto((ZXchar c));	/* needed to comfort dumb MS Visual C */
397 
398 private bool
aux_complete(c)399 aux_complete(c)
400 ZXchar	c;
401 {
402 	if (comp_flags & CASEIND) {
403 		char	lc;
404 		char	*lp;
405 
406 		for (lp = linebuf; (lc = *lp) != '\0'; lp++)
407 			*lp = CharDowncase(lc);
408 	}
409 	if (c == '?') {
410 		int	i;
411 		size_t	len = strlen(linebuf);
412 
413 		if (InJoverc)
414 			complain("[invalid `?']");
415 		/* kludge: in case we're using UseBuffers, in which case
416 		   linebuf gets written all over (but restored by TOstop/TOabort) */
417 		strcpy(Minibuf, linebuf);
418 		TOstart("Completion");	/* for now ... */
419 		for (i = 0; Possible[i] != NULL && !TOabort; i++) {
420 			if ((comp_flags & ALLOW_INDEX) && len == 0)
421 				Typeout("%2d %s", i+1, Possible[i]);
422 			else if (strncmp(Possible[i], Minibuf, len) == 0)
423 				Typeout("%s", Possible[i]);
424 		}
425 
426 		TOstop();
427 	} else {
428 		/* let's do some completion! */
429 		int
430 			i,
431 			numeral,
432 			len = strlen(linebuf),
433 			minmatch = 1000,	/* init with dummy to placate picky compilers */
434 			maxmatch = 0,
435 			numfound = 0,
436 			lastmatch = -1;	/* init with dummy to placate picky compilers */
437 
438 		if (InJoverc || c == '\n')
439 			c = '\r';	/* NL is synonym for CR; InJoverc, all are treated as CR */
440 		for (i = 0; Possible[i] != NULL; i++) {
441 			int	this_len = *Possible[i] == *linebuf
442 				? numcomp(Possible[i], linebuf) : 0;
443 
444 			if (maxmatch < this_len)
445 				maxmatch = this_len;
446 			if (this_len >= len) {
447 				if (Possible[i][len] == '\0' && c != '\t') {
448 					/* an exact match */
449 					if (comp_flags & ALLOW_OLD) {
450 						comp_value = i;	/* good: done */
451 						return NO;
452 					} else {
453 						if (InJoverc)
454 							complain("[%s already exists]");
455 						add_mess(" [already exists]");
456 						SitFor(7);
457 						return YES;
458 					}
459 				}
460 				minmatch = numfound == 0
461 					? strlen(Possible[i])
462 					: min(minmatch, numcomp(Possible[lastmatch], Possible[i]));
463 				numfound += 1;
464 				lastmatch = i;
465 			}
466 		}
467 
468 		if (c == '\r' && len > 0
469 		&& (comp_flags & ALLOW_INDEX)
470 		&& chr_to_int(linebuf, 10, YES, &numeral)
471 		&& 0 < numeral && numeral <= i
472 		) {
473 			comp_value = numeral - 1;	/* accept as an index into possible */
474 			return NO;
475 		}
476 
477 		if (c == '\r' && (comp_flags & (len == 0? ALLOW_EMPTY : ALLOW_NEW))) {
478 			comp_value = -1;	/* accept as new value (perhaps empty) */
479 			return NO;
480 		}
481 		if (numfound == 1 && c != '\t' && (comp_flags & ALLOW_OLD)) {
482 			comp_value = lastmatch;
483 			return NO;
484 		}
485 		if (InJoverc)
486 			complain("[\"%s\" %s]", linebuf,
487 				numfound == 0? "unknown" : "ambiguous");
488 		if (numfound == 0) {
489 			/* Unknown: either not ALLOW_NEW (bad) or not CR (not good enough) */
490 			add_mess(" [unknown]");
491 			SitFor(7);
492 			if (maxmatch < len && (comp_flags & ALLOW_NEW) == 0) {
493 				linebuf[maxmatch] = '\0';
494 				Eol();
495 			}
496 		} else {
497 			/* Ambiguous (or unique, but TAB): extend as much as
498 			 * possible without precluding any of the ambiguous matches.
499 			 * Explain if we were expected to be done (CR)
500 			 * or if we made no progress.
501 			 */
502 			null_ncpy(linebuf, Possible[lastmatch], (size_t) minmatch);
503 			Eol();
504 			if (c == '\r' || minmatch == len) {
505 				add_mess(numfound == 1? " [complete]" : " [ambiguous]");
506 				SitFor(7);
507 			}
508 		}
509 	}
510 	return YES;
511 }
512 
513 int
complete(possible,def,prompt,flags)514 complete(possible, def, prompt, flags)
515 register char	*possible[];
516 const char	*def;
517 const char	*prompt;
518 int	flags;
519 {
520 	/* protect static "Possible" etc. from being overwritten due to recursion */
521 	if (InRealAsk)
522 		complain((char *) NULL);
523 
524 	Possible = possible;
525 	comp_flags = flags;
526 	(void) do_ask("\r\n \t?", aux_complete, def, prompt);
527 	return comp_value;
528 }
529 
530 void
Source()531 Source()
532 {
533 	char
534 		fnamebuf[FILESIZE];
535 	bool	silence = is_an_arg();
536 
537 	swritef(fnamebuf, sizeof(fnamebuf),
538 #ifdef MSFILESYSTEM
539 		"%s/jove.rc",
540 #else
541 		"%s/.joverc",
542 #endif
543 		HomeDir);
544 	(void) ask_file((char *)NULL, fnamebuf, fnamebuf);
545 	if (!joverc(fnamebuf) && !silence) {
546 		message(IOerr("read", fnamebuf));
547 		complain((char *)NULL);
548 	}
549 }
550 
551 void
BufPos()552 BufPos()
553 {
554 	register LinePtr	lp = curbuf->b_first;
555 	register int
556 		i,
557 		dotline = 0;	/* avoid uninitialized complaint from gcc -W */
558 	long	dotchar = 0;	/* avoid uninitialized complaint from gcc -W */
559 	long	nchars;
560 
561 	for (i = nchars = 0; lp != NULL; i++, lp = lp->l_next) {
562 		if (lp == curline) {
563 			dotchar = nchars + curchar;
564 			dotline = i + 1;
565 		}
566 		nchars += length(lp) + (lp->l_next != NULL);	/* include the NL */
567 	}
568 
569 	f_mess("[\"%s\" line %d/%d, char %D/%D (%d%%), cursor = %d/%d]",
570 	       filename(curbuf), dotline, i, dotchar, nchars,
571 	       (nchars == 0) ? 100 : (int) (((long) dotchar * 100) / nchars),
572 	       calc_pos(linebuf, curchar),
573 	       calc_pos(linebuf, (int)strlen(linebuf)));
574 	stickymsg = YES;
575 }
576 
577 #ifdef SUBSHELL
578 
579 private bool
do_if(cmd)580 do_if(cmd)
581 char	*cmd;
582 {
583 	char	*args[12];
584 
585 	/* Parse cmd into an argv.  Handle various quotes
586 	 * but not environment variable references.
587 	 */
588 	{
589 		char
590 			*ip = cmd,
591 			*op = cmd,
592 			**ap = args;
593 
594 		for (;;) {
595 			while (jiswhite(*ip))
596 			    ip++;
597 			if (*ip == '\0')
598 				break;
599 			if (ap == &args[elemsof(args)])
600 				complain("Too many args for IF shell command");
601 			*ap++ = op;
602 			for (;;) {
603 				char
604 					c = *ip++,
605 					c2;
606 
607 				switch (c) {
608 				case '\0':
609 					ip -= 1;
610 					/*FALLTHROUGH*/
611 				case ' ':
612 				case '\t':
613 					break;
614 				case '"':
615 				case '\'':
616 					while ((c2 = *ip++) != c) {
617 						switch (c2) {
618 						case '\0':
619 							complain("Unpaired quote in IF command");
620 							/*NOTREACHED*/
621 						case '\\':
622 							if (c == '"') {
623 								c2 = *ip++;
624 								if (c2 == '\0')
625 									complain("Misplaced \\ in IF command");
626 							}
627 							/*FALLTHROUGH*/
628 						default:
629 							*op++ = c2;
630 							break;
631 						}
632 					}
633 					continue;
634 				case '\\':
635 					c = *ip++;
636 					if (c == '\0')
637 						complain("Misplaced \\ in IF command");
638 					/*FALLTHROUGH*/
639 				default:
640 					*op++ = c;
641 					continue;
642 				}
643 				break;
644 			}
645 			*op++ = '\0';
646 		}
647 		*ap = NULL;
648 	}
649 
650 	/* Exec the parsed command */
651 # ifdef UNIX
652 	{
653 		wait_status_t status;
654 
655 		switch (ChildPid = fork()) {
656 		case -1:
657 			complain("[Fork failed for IF: %s]", strerror(errno));
658 			/*NOTREACHED*/
659 
660 		case 0:
661 			close(0);	/*	we want reads to fail */
662 			/* close(1);	 but not writes or ioctl's */
663 			/* close(2); */
664 			(void) execvp(args[0], args);
665 			_exit(errno);
666 			/*NOTREACHED*/
667 		}
668 		dowait(&status);
669 		if (!WIFEXITED(status))
670 			complain("[no status returned from child in IF test]");
671 		if (WIFSIGNALED(status))
672 			complain("[IF test terminated by signal %d]", WTERMSIG(status));
673 		return WEXITSTATUS(status)==0;
674 	}
675 # else
676 #  ifdef MSDOS_PROCS
677 	{
678 		int	status;
679 
680 		if ((status = spawnvp(0, args[0], args)) < 0)
681 			complain("[Spawn failed: IF]");
682 		return (status == 0);	/* 0 means successful */
683 	}
684 #  else /* !MSDOS_PROCS */
685 	I do not know how to do this
686 #  endif /* !MSDOS_PROCS */
687 # endif /* !UNIX */
688 }
689 #endif /* SUBSHELL */
690 
691 private bool
cmdmatch(inp,verb,oppat)692 cmdmatch(inp, verb, oppat)
693 char	*inp;
694 char	*verb;
695 char	*oppat;
696 {
697 	int	len = strlen(verb);
698 
699 	if (caseeqn(inp, verb, (size_t)len) && LookingAt("[ \\t]\\|$", inp, len)) {
700 		if (!LookingAt(oppat, inp, len))
701 			complain("[malformed %s]", verb);
702 		return YES;
703 	}
704 	return NO;
705 }
706 
707 bool
joverc(file)708 joverc(file)
709 char	*file;
710 {
711 	char	buf[LBSIZE],
712 		lbuf[LBSIZE];
713 
714 	jmp_buf	savejmp;
715 	volatile int	lnum = 0;
716 	File	*volatile fp;
717 	volatile bool	eof = NO;
718 	volatile unsigned int	/* bitstrings */
719 			finger = 1,
720 			skipping = 0,
721 			inelse = 0;
722 
723 	fp = open_file(file, buf, F_READ, NO);
724 	if (fp == NULL)
725 		return NO;
726 
727 	/* Catch any errors, here, and do the right thing with them,
728 	   and then restore the error handle to whoever did a setjmp
729 	   last. */
730 
731 	InJoverc += 1;
732 	push_env(savejmp);
733 	if (setjmp(mainjmp)) {
734 		Buffer	*savebuf = curbuf;
735 
736 		SetBuf(do_select((Window *)NULL, "RC errors"));
737 		ins_str(sprint("%s:%d:%s", pr_name(file, YES), lnum, lbuf));
738 		ins_str(sprint("\t%s\n", mesgbuf));
739 		unmodify();
740 		SetBuf(savebuf);
741 		Asking = NO;
742 	}
743 	while (!eof) {
744 		/* This peculiar delayed EOF testing allows the last line to
745 		 * end without a NL.  We add NL later, so we leave room for it.
746 		 */
747 		eof = f_gets(fp, lbuf, sizeof(lbuf)-1);
748 		lnum += 1;
749 		Inputp = lbuf;
750 		while (jiswhite(*Inputp))
751 			Inputp += 1;	/* skip white space */
752 		if (*Inputp == '#' || *Inputp == '\0') {
753 			/* a comment */
754 #ifdef SUBSHELL
755 		} else if (cmdmatch(Inputp, "if", "[ \t]*\\(.*\\)$")) {
756 			char	cmd[128];
757 
758 			finger <<= 1;
759 			if (finger == 0)
760 				complain("[`if' nested too deeply]");
761 
762 			putmatch(1, cmd, sizeof cmd);
763 			if (skipping == 0 && !do_if(cmd))
764 				skipping |= finger;
765 #endif /* SUBSHELL */
766 #ifndef MAC	/* no environment in MacOS */
767 		} else if (cmdmatch(Inputp, "ifenv", "\\>[ \t]*\\<\\([^ \t][^ \t]*\\)\\>[ \t]\\(.*\\)$")) {
768 			finger <<= 1;
769 			if (finger == 0)
770 				complain("[`ifenv' nested too deeply]");
771 			if (skipping == 0) {
772 				char	envname[128],
773 					envpat[128],
774 					*envval;
775 
776 				putmatch(1, envname, sizeof envname);
777 				putmatch(2, envpat, sizeof envpat);
778 				envval = getenv(envname);
779 				if (envval==NULL || !LookingAt(envpat, envval, 0))
780 					skipping |= finger;
781 			}
782 #endif
783 		} else if (cmdmatch(Inputp, "else", "[ \\t]*$")) {
784 			if (finger == 1 || (inelse & finger))
785 				complain("[Unexpected `else']");
786 			inelse |= finger;
787 			skipping ^= finger;
788 		} else if (cmdmatch(Inputp, "endif", "[ \\t]*$")) {
789 			if (finger == 1)
790 				complain("[Unexpected `endif']");
791 			inelse &= ~finger;
792 			skipping &= ~finger;
793 			finger >>= 1;
794 		} else if (skipping == 0) {
795 			(void) strcat(Inputp, "\n");
796 			for (;;) {
797 				ZXchar	c;
798 
799 				cmd_sync();
800 				this_cmd = OTHER_CMD;
801 
802 				if ((c = ZXC(*Inputp)) == '-' || jisdigit(c)) {
803 					LastKeyStruck = c;
804 					Inputp += 1;
805 					Digit();
806 				} else {
807 					Extend();
808 				}
809 
810 				/* get any pending input hiding in the peek buffer */
811 
812 				if ((c = peekchar) != EOF) {
813 					peekchar = EOF;
814 					if (Inputp != NULL && ZXRC(Inputp[-1]) == c)
815 						Inputp -= 1;
816 					else if (!jiswhite(c) && c != '\n' && c != '\0')
817 						complain("[junk at end of line]");
818 				}
819 
820 				if (Inputp == NULL)
821 					break;
822 				while (jiswhite(*Inputp))
823 					Inputp += 1;	/* skip white space */
824 				if (*Inputp == '\0' || *Inputp == '\n')
825 					break;
826 				if (this_cmd != ARG_CMD)
827 					complain("[junk at end of line]");
828 			}
829 		}
830 	}
831 
832 #ifdef SUBSHELL
833 	if (finger != 1) {
834 		finger = 1;
835 		complain("[Missing endif]");
836 	}
837 #endif
838 	f_close(fp);
839 	pop_env(savejmp);
840 	Inputp = NULL;
841 	Asking = NO;
842 	InJoverc -= 1;
843 	return YES;
844 }
845