]>
Commit | Line | Data |
---|---|---|
053a78b2 | 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"); | |
f631c332 | 107 | cooked_mode(); |
053a78b2 | 108 | kill(getpid(), SIGSTOP); |
f631c332 | 109 | raw_mode(); |
053a78b2 | 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 | for (ln = line; n && state == MATCH; best && (n = best->p_next)) { | |
160 | last = best; | |
161 | state = single_parse(&ln, n, &best, &ambig, &nomatch); | |
162 | } | |
163 | if (*ln && ((best && best->p_menu) || n == NULNODE)) | |
164 | state = NOMATCH; | |
165 | switch (state) { | |
166 | case NOMATCH: | |
167 | if (!*ln) { | |
168 | if (last && last->p_menu && *line) | |
169 | printf("Carriage return, or "); | |
170 | break; | |
171 | } | |
172 | ln += nomatch; | |
173 | *ln = 0; | |
174 | write(1, "\r\nNOT a valid command line", 26); | |
175 | return; | |
176 | case AMBIG: | |
177 | write(1, "one of the following:\r\n ", 26); | |
178 | for (; ambig; ambig = ambig->p_link) | |
179 | printf("%s ", ambig->p_word); | |
180 | fflush(stdout); | |
181 | return; | |
182 | case INCOMP: | |
183 | printf("one of the following:\r\n %s", best->p_word); | |
184 | fflush(stdout); | |
185 | return; | |
186 | default: | |
187 | write(1, eolmsg, strlen(eolmsg)); | |
188 | return; | |
189 | } | |
190 | kw = 0; | |
191 | for (nd = n; nd; nd = nd->p_peer) { | |
192 | if (!kw) { | |
193 | write(1, "one of the following:\r\n ", 26); | |
194 | kw = 3; | |
195 | } | |
196 | if (kw + strlen(nd->p_word) > 72) { | |
197 | printf("\r\n %s ", nd->p_word); | |
198 | fflush(stdout); | |
199 | kw = 4 + strlen(nd->p_word); | |
200 | } else { | |
201 | printf("%s ", nd->p_word); | |
202 | fflush(stdout); | |
203 | kw += 1 + strlen(nd->p_word); | |
204 | } | |
205 | } | |
206 | } | |
207 | ||
208 | ||
209 | /* Do escape processing. If a unique completion exists, use it. Otherwise, | |
210 | * do the same as ?. | |
211 | */ | |
212 | ||
213 | do_complete(line, nod, prompt) | |
214 | char *line; | |
215 | struct parse_node *nod; | |
216 | char *prompt; | |
217 | { | |
218 | struct parse_node *n, *nd; | |
219 | char *ln; | |
220 | char *tmp; | |
221 | int state, nomatch; | |
222 | struct parse_node *best, *ambig; | |
223 | ||
224 | ln = line; | |
225 | state = MATCH; | |
226 | nd = nod; | |
227 | while (state == MATCH) { | |
228 | tmp = ln; | |
229 | state = single_parse(&ln, nd, &best, &ambig, &nomatch); | |
230 | if (state == MATCH) | |
231 | nd = best->p_next; | |
232 | } | |
233 | switch (state) { | |
234 | case AMBIG: | |
235 | /* printf("ln %X, tmp %X, ln - tmp %d, nomatch %d\r\n", ln, tmp, ln-tmp, nomatch); | |
236 | if (ln - tmp < nomatch) { | |
237 | printf("attempting partial complete\r\n"); | |
238 | fflush(stdout); | |
239 | sleep(1); | |
240 | while ((ln > line) && !isspace(*ln)) | |
241 | ln--; | |
242 | tmp = ambig->p_word; | |
243 | while (nomatch--) | |
244 | *ln++ = *tmp++; | |
245 | *ln = 0; | |
246 | putchar(7); | |
247 | return; | |
248 | } */ | |
249 | /* fall through to: */ | |
250 | case NOMATCH: | |
251 | if (!(nd) || (nd->p_peer)) { | |
252 | write(1, " ", 2); | |
253 | do_help(line, nod, prompt); | |
254 | printf("\r\n%s%s", prompt, line); | |
255 | fflush(stdout); | |
256 | return; | |
257 | } | |
258 | best = nd; | |
259 | *ln++ = 'x'; | |
260 | /* fall through to incomplete case */ | |
261 | case INCOMP: | |
262 | ln = tmp; | |
263 | do { | |
264 | tmp = best->p_word; | |
265 | while (*tmp) | |
266 | *ln++ = *tmp++; | |
267 | *ln++ = ' '; | |
268 | *ln = 0; | |
269 | } while (best->p_next && !best->p_next->p_peer && | |
270 | !best->p_menu && (best = best->p_next)); | |
271 | printf("\r%s%s", prompt, line); | |
272 | fflush(stdout); | |
273 | break; | |
274 | default: | |
275 | write(1, "We shouldn't get here (parser error)\r\n", 38); | |
276 | } | |
277 | } | |
278 | ||
279 | ||
280 | /* Single parse parses through a single level of the parse tree. | |
281 | * There are 4 possible outcomes: | |
282 | * an exact match is made: the matching node is returned, ambig = 0 | |
283 | * an incomplete match: the matching node is returned, ambig = node | |
284 | * ambiguous: nothing is returned, ambig = list of completions | |
285 | * no matches: nothing is returned, ambig = 0 | |
286 | */ | |
287 | ||
288 | int single_parse(line, nd, best, ambig, nomatch) | |
289 | char **line; | |
290 | struct parse_node *nd; | |
291 | struct parse_node **best; | |
292 | struct parse_node **ambig; | |
293 | int *nomatch; | |
294 | { | |
295 | char *p; | |
296 | char c; /* char we're working on (from line) */ | |
297 | struct parse_node *n; /* node loop counter */ | |
298 | struct parse_node *tail; /* tmp used to build chains */ | |
299 | int i; /* loop counter */ | |
300 | int match; /* how many chars have we matched? */ | |
301 | int len; /* length of this keyword */ | |
302 | ||
303 | #ifdef DEBUG | |
304 | printf("single_parse(\"%s\") -> ", *line); | |
305 | #endif /* DEBUG */ | |
306 | *ambig = tail = *best = NULNODE; | |
307 | match = *nomatch = 0; | |
308 | /* skip leading whitespace */ | |
309 | while (isspace(**line)) | |
310 | (*line)++; | |
311 | /* step through each node */ | |
312 | for (n = nd; n; n = n->p_peer) { | |
313 | len = strlen(n->p_word); | |
314 | /* step through each character in line */ | |
315 | for (i = 0; 1; i++) { | |
316 | /* if at end of word on line */ | |
317 | if (isspace((*line)[i]) || (*line)[i] == 0) { | |
318 | /* another ambiguous match */ | |
319 | if (i == match && i) { | |
320 | tail->p_link = n; | |
321 | tail = n; | |
322 | n->p_link = NULNODE; | |
323 | } | |
324 | /* a better match */ | |
325 | if (i > match) { | |
326 | match = i; | |
327 | *best = tail = *ambig = n; | |
328 | n->p_link = NULNODE; | |
329 | } | |
330 | break; | |
331 | } | |
332 | if (isupper(c = (*line)[i])) | |
333 | c = tolower(c); | |
334 | if (c != n->p_word[i]) { | |
335 | if (i > *nomatch) | |
336 | *nomatch = i; | |
337 | break; | |
338 | } | |
339 | } | |
340 | } | |
341 | if (match > 0) { | |
342 | (*line) += match; | |
343 | if (tail != *ambig) { | |
344 | *best = NULNODE; | |
345 | *nomatch = match; | |
346 | if (isspace(**line)) { | |
347 | #ifdef DEBUG | |
348 | printf("NOMATCH\n"); | |
349 | #endif /* DEBUG */ | |
350 | return(NOMATCH); | |
351 | } else { | |
352 | #ifdef DEBUG | |
353 | printf("AMBIG\n"); | |
354 | #endif /* DEBUG */ | |
355 | return(AMBIG); | |
356 | } | |
357 | } | |
358 | if (isspace(**line)) { | |
359 | *ambig = NULNODE; | |
360 | while (isspace(**line)) | |
361 | (*line)++; | |
362 | #ifdef DEBUG | |
363 | printf("MATCH\n"); | |
364 | #endif /* DEBUG */ | |
365 | return(MATCH); | |
366 | } | |
367 | #ifdef DEBUG | |
368 | printf("INCOMP\n"); | |
369 | #endif /* DEBUG */ | |
370 | return(INCOMP); | |
371 | } | |
372 | *ambig = tail = *best = NULNODE; | |
373 | #ifdef DEBUG | |
374 | printf("NOMATCH\n"); | |
375 | #endif /* DEBUG */ | |
376 | return(NOMATCH); | |
377 | } | |
378 | ||
379 | ||
380 | /* execute the line. First check to see that the line is legal. If not, | |
381 | * do_help the line & return ERROR. If so, execute each node passed through, | |
382 | * and return OK (or EXIT if an EXIT node was encountered). | |
383 | */ | |
384 | ||
385 | int do_parse(line, nod, prompt) | |
386 | char *line; | |
387 | struct parse_node *nod; | |
388 | char *prompt; | |
389 | { | |
390 | struct parse_node *n, *nd, *last; | |
391 | char *ln, *tmp; | |
392 | int state, i; | |
393 | struct parse_node *best, *ambig; | |
394 | int nomatch; | |
395 | EntryForm *f; | |
396 | ||
397 | ln = line; | |
398 | n = nod; | |
399 | state = MATCH; | |
400 | best = NULNODE; | |
401 | while (n && ((state == MATCH) || (state == INCOMP))) { | |
402 | last = best; | |
403 | state = single_parse(&ln, n, &best, &ambig, &nomatch); | |
404 | #ifdef DEBUG | |
405 | printf("best = %s, best->next = 0x%x\r\n", best ? best->p_word : "", | |
406 | best ? best->p_next : 0); | |
407 | #endif /* DEBUG */ | |
408 | if ((state == NOMATCH) && (!*ln) && (n == nod)) | |
409 | return(OK); | |
410 | if ((state == MATCH) || (state == INCOMP)) { | |
411 | n = best->p_next; | |
412 | } | |
413 | } | |
414 | if (((state == AMBIG) || (state == NOMATCH)) && !*ln) | |
415 | for (; n; n = n->p_peer) | |
416 | if (last && last->p_menu) { | |
417 | n = last; | |
418 | state = MATCH; | |
419 | break; | |
420 | } | |
421 | if (state == NOMATCH && !*ln) { | |
422 | while (last && last->p_next && !last->p_next->p_peer && !last->p_menu) | |
423 | last = last->p_next; | |
424 | if (last && last->p_menu) { | |
425 | state = MATCH; | |
426 | best = last; | |
427 | } | |
428 | } | |
429 | ||
430 | if ((state == NOMATCH) || (state == AMBIG)) { | |
431 | write(1, " BAD command, ", 16); | |
432 | do_help(line, nod, prompt); | |
433 | return(ERROR); | |
434 | } | |
435 | if (!best) | |
436 | best = n; | |
437 | write(1, "\r\n", 2); | |
438 | cooked_mode(); | |
439 | for (i = 0; line[i] && !isspace(line[i]); i++); | |
440 | if (!strncmp("help", line, i)) | |
441 | help(best->p_menu->form); | |
442 | else | |
443 | MoiraMenuRequest(best->p_menu); | |
444 | raw_mode(); | |
445 | return(OK); | |
446 | } |