]> andersk Git - moira.git/blob - clients/mmoira/parser.c
sync'ing files for RCS->CVS migration
[moira.git] / clients / mmoira / parser.c
1 /* $Header$
2  *
3  * TOPS-20 style command parser
4  *
5  */
6
7 #include <stdio.h>
8 #include <ctype.h>
9 #include <sys/signal.h>
10 #include <X11/Intrinsic.h>
11 #include "mmoira.h"
12 #include "parser.h"
13
14
15 /* trivial absolute-value macro */
16 #define abs(i)  ((i) < 0 ? -(i) : (i))
17
18 /* message used when end of parse tree is reached */
19 char    eolmsg[] = "return to execute command";
20
21
22 /* main entry point.  Takes the prompt and top node of the parse tree, and
23  * will collect user's input, providing help and completion.  When entry
24  * is complete (<return> is pressed), the actions called for by each node
25  * in the parse tree which is traversed will be executed.
26  */
27
28 int     parser(prompt, nd)
29 char    *prompt;
30 struct parse_node       *nd;
31 {
32     char line[BUFLEN];
33     char *p, c;
34     int val;
35
36     p = &line[0];
37     *p = 0;
38     write(1, prompt, strlen(prompt));
39     for (c = (getchar() & 0x7F); 1; c = (getchar() & 0x7F)) {
40         if (c == 0)
41           continue;
42         switch (c) {
43         case 127:
44         case '\b':
45             if (p == &line[0]) {
46                 putchar(7);
47                 break;
48             }
49             if (*(--p) == 'I' - '@') {
50                 *p = 0;
51                 printf("\r%s%s", prompt, line);
52                 fflush(stdout);
53                 break;
54             }
55             *p = 0;
56             write(1, "\b \b", 3);
57             break;
58         case 'C' - '@':
59         case 'G' - '@':
60             write(1, " \007ABORT\r\n", 9);
61             return(ABORT);
62         case 'Q' - '@':
63         case 'V' - '@':
64             putchar('\\');
65             c = getchar();
66             if (c < ' ')
67               printf("\b^%c", c + '@');
68             else
69               printf("\b%c", c);
70             fflush(stdout);
71             *p++ = c;
72             break;
73         case 'W' - '@':
74             if (p > &line[0])
75               p--;
76             while ((p >= &line[0]) && isspace(*p)) {
77                 write(1, "\b \b", 3);
78                 p--;
79             }
80             while ((p >= &line[0]) && !isspace(*p)) {
81                 write(1, "\b \b", 3);
82                 p--;
83             }
84             if (p > &line[0]) {
85                 p++;
86             } else {
87                 p = &line[0];
88             }
89             *p = 0;
90             break;
91         case '?':
92             write(1, "?  ", 3);
93             *p = 0;
94             do_help(&line[0], nd, prompt);
95             p = &line[strlen(line)];
96             printf("\n\r%s%s", prompt, line);
97             fflush(stdout);
98             break;
99         case '[' - '@':
100         case '\t':
101             *p = 0;
102             do_complete(&line[0], nd, prompt);
103             p = &line[strlen(line)];
104             break;
105         case 'Z' - '@':
106             printf("\r\n");
107             cooked_mode();
108             kill(getpid(), SIGSTOP);
109             raw_mode();
110             /* when continued, fall through to */
111         case 'R' - '@':
112             *p = 0;
113             printf("\r\n%s%s", prompt, line);
114             fflush(stdout);
115             break;
116         case 'U' - '@':
117             while (p-- > &line[0])
118               write(1, "\b \b", 3);
119             *(++p) = 0;
120             printf("\r%s", prompt);
121             fflush(stdout);
122             break;
123         case '\n':
124         case '\r':
125             if ((val = do_parse(line, nd, prompt)) != ERROR) {
126                 write(1, "\r\n", 2);
127                 return(val);
128             }
129             p = &line[strlen(line)];
130             *p = 0;
131             printf("\r\n%s%s", prompt, line);
132             fflush(stdout);
133             break;
134         default:
135             putchar(c);
136             *p++ = c;
137             *p = 0;
138         }
139     }
140 }
141
142
143 /* called when a ? is typed.  This parses the line as far as possible, then
144  * displays possible completions and help strings.
145  */
146
147 do_help(line, nod, prompt)
148 char *line;
149 struct parse_node *nod;
150 char *prompt;
151 {
152     char *ln;
153     struct parse_node *n, *nd, *last;
154     int kw, state, nomatch;
155     struct parse_node *best, *ambig;
156
157     n = nod;
158     state = MATCH;
159     best = NULNODE;
160     for (ln = line; n && state == MATCH; best && (n = best->p_next)) {
161         last = best;
162         state = single_parse(&ln, n, &best, &ambig, &nomatch);
163     }
164     if (*ln && ((best && best->p_menu) || n == NULNODE))
165       state = NOMATCH;
166     switch (state) {
167     case NOMATCH:
168         if (!*ln) {
169             if (last && last->p_menu  && *line)
170               printf("Carriage return, or ");
171             break;
172         }
173         ln += nomatch;
174         *ln = 0;
175         write(1, "\r\nNOT a valid command line", 26);
176         return;
177     case AMBIG:
178         write(1, "one of the following:\r\n   ", 26);
179         for (; ambig; ambig = ambig->p_link)
180           printf("%s ", ambig->p_word);
181         fflush(stdout);
182         return;
183     case INCOMP:
184         printf("one of the following:\r\n   %s", best->p_word);
185         fflush(stdout);
186         return;
187     default:
188         write(1, eolmsg, strlen(eolmsg));
189         return;
190     }
191     kw = 0;
192     for (nd = n; nd; nd = nd->p_peer) {
193         if (!kw) {
194             write(1, "one of the following:\r\n   ", 26);
195             kw = 3;
196         }
197         if (kw + strlen(nd->p_word) > 72) {
198             printf("\r\n   %s ", nd->p_word);
199             fflush(stdout);
200             kw = 4 + strlen(nd->p_word);
201         } else {
202             printf("%s ", nd->p_word);
203             fflush(stdout);
204             kw += 1 + strlen(nd->p_word);
205         }
206     }
207 }
208
209
210 /* Do escape processing.  If a unique completion exists, use it.  Otherwise,
211  * do the same as ?.
212  */
213
214 do_complete(line, nod, prompt)
215 char *line;
216 struct parse_node *nod;
217 char *prompt;
218 {
219     struct parse_node *n, *nd;
220     char *ln;
221     char *tmp;
222     int state, nomatch;
223     struct parse_node *best, *ambig;
224
225     ln = line;
226     state = MATCH;
227     nd = nod;
228     while (state == MATCH) {
229         tmp = ln;
230         state = single_parse(&ln, nd, &best, &ambig, &nomatch);
231         if (state == MATCH)
232           nd = best->p_next;
233     }
234     switch (state) {
235     case AMBIG:
236 /*      printf("ln %X, tmp %X, ln - tmp %d, nomatch %d\r\n", ln, tmp, ln-tmp, nomatch);
237         if (ln - tmp < nomatch) {
238             printf("attempting partial complete\r\n");
239             fflush(stdout);
240             sleep(1);
241             while ((ln > line) && !isspace(*ln))
242               ln--;
243             tmp = ambig->p_word;
244             while (nomatch--)
245               *ln++ = *tmp++;
246             *ln = 0;
247             putchar(7);
248             return;
249         } */
250         /* fall through to: */
251     case NOMATCH:
252         if (!(nd) || (nd->p_peer)) {
253             write(1, "  ", 2);
254             do_help(line, nod, prompt);
255             printf("\r\n%s%s", prompt, line);
256             fflush(stdout);
257             return;
258         }
259         best = nd;
260         *ln++ = 'x';
261         /* fall through to incomplete case */
262     case INCOMP:
263         ln = tmp;
264         do {
265             tmp = best->p_word;
266             while (*tmp)
267               *ln++ = *tmp++;
268             *ln++ = ' ';
269             *ln = 0;
270         } while (best->p_next && !best->p_next->p_peer &&
271                  !best->p_menu && (best = best->p_next));
272         printf("\r%s%s", prompt, line);
273         fflush(stdout);
274         break;
275     default:
276         write(1, "We shouldn't get here (parser error)\r\n", 38);
277     }
278 }
279
280
281 /* Single parse parses through a single level of the parse tree.
282  * There are 4 possible outcomes:
283  *      an exact match is made: the matching node is returned, ambig = 0
284  *      an incomplete match: the matching node is returned, ambig = node
285  *      ambiguous: nothing is returned, ambig = list of completions
286  *      no matches: nothing is returned, ambig = 0
287  */
288
289 int single_parse(line, nd, best, ambig, nomatch)
290 char **line;
291 struct parse_node *nd;
292 struct parse_node **best;
293 struct parse_node **ambig;
294 int *nomatch;
295 {
296     char *p;
297     char c;                     /* char we're working on (from line) */
298     struct parse_node *n;       /* node loop counter */
299     struct parse_node *tail;    /* tmp used to build chains */
300     int i;                      /* loop counter */
301     int match;                  /* how many chars have we matched? */
302     int len;                    /* length of this keyword */
303
304 #ifdef DEBUG
305     printf("single_parse(\"%s\") -> ", *line);
306 #endif /* DEBUG */
307     *ambig = tail = *best = NULNODE;
308     match = *nomatch = 0;
309     /* skip leading whitespace */
310     while (isspace(**line))
311       (*line)++;
312     /* step through each node */
313     for (n = nd; n; n = n->p_peer) {
314         len = strlen(n->p_word);
315         /* step through each character in line */
316         for (i = 0; 1; i++) {
317             /* if at end of word on line */
318             if (isspace((*line)[i]) || (*line)[i] == 0) {
319                 /* another ambiguous match */
320                 if (i == match && i) {
321                     tail->p_link = n;
322                     tail = n;
323                     n->p_link = NULNODE;
324                 }
325                 /* a better match */
326                 if (i > match) {
327                     match = i;
328                     *best = tail = *ambig = n;
329                     n->p_link = NULNODE;
330                 }
331                 break;
332             }
333             if (isupper(c = (*line)[i]))
334               c = tolower(c);
335             if (c != n->p_word[i]) {
336                 if (i > *nomatch)
337                   *nomatch = i;
338                 break;
339             }
340         }
341     }
342     if (match > 0) {
343         (*line) += match;
344         if (tail != *ambig) {
345             *best = NULNODE;
346             *nomatch = match;
347             if (isspace(**line)) {
348 #ifdef DEBUG
349                 printf("NOMATCH\n");
350 #endif /* DEBUG */
351                 return(NOMATCH);
352             } else {
353 #ifdef DEBUG
354                 printf("AMBIG\n");
355 #endif /* DEBUG */
356                 return(AMBIG);
357             }
358         }
359         if (isspace(**line)) {
360             *ambig = NULNODE;
361             while (isspace(**line))
362               (*line)++;
363 #ifdef DEBUG
364             printf("MATCH\n");
365 #endif /* DEBUG */
366             return(MATCH);
367         }
368 #ifdef DEBUG
369         printf("INCOMP\n");
370 #endif /* DEBUG */
371         return(INCOMP);
372     }
373     *ambig = tail = *best = NULNODE;
374 #ifdef DEBUG
375     printf("NOMATCH\n");
376 #endif /* DEBUG */
377     return(NOMATCH);
378 }
379
380
381 /* execute the line.  First check to see that the line is legal.  If not,
382  * do_help the line & return ERROR.  If so, execute each node passed through,
383  * and return OK (or EXIT if an EXIT node was encountered).
384  */
385
386 int do_parse(line, nod, prompt)
387 char *line;
388 struct parse_node *nod;
389 char *prompt;
390 {
391     struct parse_node *n, *nd, *last;
392     char *ln, *tmp;
393     int state, i;
394     struct parse_node *best, *ambig;
395     int nomatch;
396     EntryForm *f;
397
398     ln = line;
399     n = nod;
400     state = MATCH;
401     best = NULNODE;
402     while (n && ((state == MATCH) || (state == INCOMP))) {
403         last = best;
404         state = single_parse(&ln, n, &best, &ambig, &nomatch);
405 #ifdef DEBUG
406         printf("best = %s, best->next = 0x%x\r\n", best ? best->p_word : "",
407                best ? best->p_next : 0);
408 #endif /* DEBUG */
409         if ((state == NOMATCH) && (!*ln) && (n == nod))
410           return(OK);
411         if ((state == MATCH) || (state == INCOMP)) {
412             n = best->p_next;
413         }
414     }
415     if (((state == AMBIG) || (state == NOMATCH)) && !*ln)
416       for (; n; n = n->p_peer)
417         if (last && last->p_menu) {
418             n = last;
419             state = MATCH;
420             break;
421         }
422     if (state == NOMATCH && !*ln) {
423         while (last && last->p_next && !last->p_next->p_peer && !last->p_menu)
424           last = last->p_next;
425         if (last && last->p_menu) {
426             state = MATCH;
427             best = last;
428         }
429     }
430
431     if ((state == NOMATCH) || (state == AMBIG)) {
432         write(1, "  BAD command,  ", 16);
433         do_help(line, nod, prompt);
434         return(ERROR);
435     }
436     if (!best)
437       best = n;
438     write(1, "\r\n", 2);
439     cooked_mode();
440     for (i = 0; line[i] && !isspace(line[i]); i++);
441     if (!strncmp("help", line, i))
442       help(best->p_menu->form);
443     else
444       MoiraMenuRequest(best->p_menu);
445     raw_mode();
446     return(OK);
447 }
This page took 2.990134 seconds and 5 git commands to generate.