]>
Commit | Line | Data |
---|---|---|
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 | } |