1: /* Copyright (c) Stichting Mathematisch Centrum, Amsterdam, 1985. */
2:
3: /* $Header: getc.c,v 2.5 85/08/22 16:02:44 timo Exp $ */
4:
5: /* B editor -- read key definitions from file */
6:
7: #include "b.h"
8: #include "feat.h"
9: #ifdef LINDA
10: #include "b1mem.h"
11: #define syserr EDsyserr
12: #else !LINDA
13: #define freemem(p) free(p)
14: #endif !LINDA
15: #include "file.h"
16: #include "keys.h"
17:
18: #include <ctype.h>
19:
20: extern bool dflag;
21:
22: #define ESC '\033'
23:
24: /*
25: This file contains a little parser for key definition files.
26: To allow sufficient freedom in preparing such a file, a simple
27: grammar has been defined according to which the file is parsed.
28: The parsing process is extremely simple, as it can be done
29: top-down using recursive descent.
30:
31:
32: Lexical conventions:
33:
34: - Blanks between lexical symbols are gnored.
35: - From '#' to end of line is comment (except inside strings).
36: - Strings are delimited by single or double quotes and
37: use the same escape sequences as C strings, plus:
38: \e or \E means an ESCape ('\033').
39: - Command names are like C identifiers ([a-zA-Z_][a-zA-Z0-9_]*).
40: Upper/lower case distinction is significant.
41: - numbers are octal or decimal integers in C-style (leading zero means octal)
42: - After '^' a character is expected, this must be a letter or one of @^_[]\ .
43:
44: Syntax in modified BNF ([] mean 0 or 1, * means 0 or more, + means 1 or more):
45:
46: file: line*
47: line: [def] [comment]
48: def: commandname '=' rhs
49: rhs: item+
50: item: string | '^' character | number
51:
52:
53: Notes:
54:
55: - A definition for command "term_init" defines a string to be sent
56: TO the terminal at initialization time, e.g. to set programmable
57: function key definitions. Similar for "term_done" on exiting.
58: - Command names are conventional editor commands.
59:
60: */
61:
62:
63: #ifndef LINDA
64: /* Defines subroutine that used to be in the support levels: */
65:
66: Hidden string getmem(nbytes)
67: unsigned nbytes;
68: {
69: string malloc();
70: string pointer= malloc(nbytes);
71:
72: if (pointer == NULL)
73: syserr("memory full in initkeys");
74: return pointer;
75: }
76:
77: Hidden string regetmem(pp, nbytes)
78: string *pp;
79: unsigned nbytes;
80: {
81: *pp= realloc(*pp, nbytes);
82: if (*pp == NULL)
83: syserr("memory full in initkeys (regetmem)");
84: }
85: #endif !LINDA
86:
87:
88: #define '#' /* Not B-like but very UNIX-like */
89: #define MAXDEFS 100
90:
91: Hidden FILE *fp; /* File from which to read */
92: Hidden string filename; /* File name for error messages */
93: Hidden char nextc; /* Next character to be analyzed */
94: Hidden bool eof; /* EOF seen? */
95: Hidden int lcount; /* Current line number */
96: Hidden bool errcount; /* Number of errors detected */
97:
98:
99: struct tabent {
100: int code;
101: string name;
102: string def;
103: };
104:
105: /* Table of key definitions, mostly filled by reading definitions from a file.
106: The "I" macro has two arguments: the default for termcap and that for
107: the IBM PC. It expands to either depending on whether IBMPC is defined.
108: 'def' fields initialized with a string starting with '=' are termcap names,
109: and are replaced by the corresponding termcap entry (NULL if none).
110: On the IBM PC, 'extended codes' are by convention a null character
111: followed by another character (usually the scan code). Since the null
112: character is rather unsuitable for use in C strings, we use \377 (hex FF)
113: instead, a code which has no assigned graphic is the extended IBM PC
114: character set. E.g., F1 is 0-59, which we encode as \377\073 (since
115: \073 is octal for 59 decimal). For the exact codes, see for instance the
116: BASIC 2.0 manual, appendix G, or the XT Technical Reference, page 2-14.
117: */
118:
119: #ifdef IBMPC
120: #define I(tc, ibm) ibm
121: #else !IBMPC
122: #define I(tc, ibm) tc
123: #endif !IBMPC
124:
125: Visible struct tabent deftab[MAXDEFS] = {
126: /* General rule:
127: unix => ctrl-x
128: IBM => alt-x
129: where x is first letter of command name
130: */
131: {0377, "ignore", NULL}, /* Entry to ignore a key */
132: {COPY, "copy", I(NULL, "\377\056")},
133: {DELETE, "delete", I(NULL, "\377\040")},
134: {DELETE, "delete", I(NULL, "\377\123")}, /* IBM DEL key */
135: {ACCEPT, "accept", I(NULL, "\377\022")}, /* ^E, alt-E */
136: {ACCEPT, "end", I(NULL, "\377\117")}, /* IBM END key */
137: {'\t', "tab", NULL}, /* = ACCEPT in Bed, insert tab in Linda */
138: {UNDO, "undo"}, /* Always backspace = ^H */
139: {REDRAW, "redraw", I(NULL, "\377\046")}, /* ^L, alt-L */
140: {REDRAW, "look"},
141: {RETURN, "newline"}, /* Always ^M */
142: {REDO, "redo", I(NULL, "\177")}, /* IBM ctrl-BS = ASCII 177 (DEL) */
143: {EXIT, "exit", I(NULL, "\377\055")}, /* ^X, alt-X */
144:
145: #ifdef RECORDING
146: /*
147: * The IBM-PC has a problem here in ANSI.SYS mode: ctrl-P is
148: * unusable because it means Print Screen, and alt-R is unusable
149: * because it transmits 0, 19 but 19 is ctrl-S which means stop
150: * output :-(.
151: * The only reasonable place to put the things would then be on
152: * function keys. You should do this in the key definitions file. (?)
153: */
154: {PLAYBACK, "play", I(NULL, "\377\031")},
155: {PLAYBACK, "playback", I(NULL, "\377\031")},
156: {RECORD, "record", I(NULL, "\377\023")},
157: #endif RECORDING
158:
159: #ifdef LINDA
160: {BFIND, "bfind", I(NULL, "\377\060")},
161: {FIND, "find", I(NULL, "\377\041")},
162: {GLOBAL, "global", I(NULL, "\377\042")},
163: {JOIN, "join", I(NULL, "\377\044")},
164: {TOGGLE, "toggle", I(NULL, "\377\024")},
165: {YANK, "yank", I(NULL, "\377\025")},
166: {LITERAL, "literal", I(NULL, "\377\057")}, /* ^V, alt-V */
167: #endif LINDA
168:
169: {WIDEN, "widen", I("=k1", "\377\073")}, /* IBM F1 */
170: {NARROW, "narrow", I("=k2", "\377\075")}, /* IBM F3 (!!!) */
171: {NARROW, "first"},
172: {RNARROW, "rnarrow", I("=k3", "\377\076")}, /* IBM F4 (!!!) */
173: {RNARROW, "last"},
174: {EXTEND, "extend", I("=k4", "\377\074")}, /* IBM F2 (!!!) */
175: {UPARROW, "up", I("=ku", "\377\110")},
176: {UPLINE, "upline", I("=k5", "\377\110")},
177: {LEFTARROW, "left", I("=kl", "\377\113")},
178: {PREVIOUS, "previous", I("=k6", NULL)},
179: {RITEARROW, "right", I("=kr", "\377\115")},
180: {NEXT, "next", I("=k7", NULL)},
181: {DOWNARROW, "down", I("=kd", "\377\120")},
182: {DOWNLINE, "downline", I("=k8", "\377\120")},
183:
184: {GOTO, "goto", I("\033g", NULL)}, /* Doesn't exist on IBM */
185: #ifdef HELPFUL
186: {HELP, "help", I("\033?", "\377\104")}, /* ESC ?, IBM F10 */
187: #endif HELPFUL
188:
189: {0, "term_init", I("=ks", NULL)},
190: {0, "term_done", I("=ke", NULL)},
191: };
192:
193: #undef I
194:
195: Hidden int ndefs;
196:
197:
198: Hidden Procedure err(fmt, arg)
199: string fmt, arg;
200: {
201: if (errcount == 0)
202: fprintf(stderr, "Errors in key definitions file:\n");
203: ++errcount;
204: fprintf(stderr, "%s, line %d: ", filename, lcount);
205: fprintf(stderr, fmt, arg);
206: fprintf(stderr, "\n");
207: }
208:
209: Hidden Procedure adv()
210: {
211: int c;
212:
213: if (eof)
214: return;
215: c= getc(fp);
216: if (c == EOF) {
217: nextc= '\n';
218: eof= Yes;
219: }
220: else {
221: nextc= c;
222: if (c == '\n')
223: ++lcount;
224: }
225: }
226:
227: Hidden Procedure skipsp()
228: {
229: while (nextc == ' ' || nextc == '\t')
230: adv();
231: }
232:
233: Hidden int lookup(name)
234: string name;
235: {
236: int i;
237:
238: for (i= 0; i < ndefs; ++i) {
239: if (deftab[i].name != NULL && strcmp(name, deftab[i].name) == 0)
240: return i;
241: }
242: return -1;
243: }
244:
245: Hidden Procedure store(code, name, def)
246: int code;
247: string name;
248: string def;
249: {
250: struct tabent *d, *last= deftab+ndefs;
251: string p, q;
252:
253: /* Undefine conflicting definitions. Conflicts arise
254: when a command definition is an initial subsequence
255: of another, or vice versa. Key definitions (code < 0)
256: are not undefined. */
257: if (code > 0) {
258: for (d= deftab; d < last; ++d) {
259: if (d->code >= 0 && d->def != NULL) {
260: for (p= def, q= d->def; *p == *q; ++p, ++q) {
261: if (*p == '\0' || *q == '\0') {
262: d->def= NULL;
263: break;
264: }
265: }
266: }
267: }
268: }
269:
270: /* Find a free slot with the same code and NULL definition */
271: /* (For code == 0, the name must match instead of the code,
272: and the definition need not be NULL) */
273: for (d= deftab; d < last; ++d) {
274: if (code == 0 ? strcmp(name, d->name) == 0
275: : (d->code == code && d->def == NULL))
276: break;
277: }
278: if (d == last) { /* Extend definition table */
279: if (ndefs >= MAXDEFS) {
280: err("Too many key definitions", "");
281: return;
282: }
283: ++ndefs;
284: d->code= code;
285: d->name= name;
286: }
287: d->def= def;
288: }
289:
290: Hidden string savestr(s)
291: string s;
292: {
293: string new;
294:
295: new= getmem((unsigned) (strlen(s) + 1));
296: strcpy(new, s);
297: return new;
298: }
299:
300: Hidden Procedure append(to, item)
301: string *to, item;
302: {
303: int len= strlen(*to) + strlen(item) + 1;
304: regetmem(to, len);
305: strcat(*to, item);
306: }
307:
308: Hidden string getname()
309: {
310: char buffer[20];
311: string bp;
312:
313: if (!isalpha(nextc) && nextc != '_') {
314: err("No name where expected", "");
315: return NULL;
316: }
317: for (bp= buffer; isalnum(nextc) || nextc == '_'; ) {
318: if (bp < buffer + sizeof buffer - 1)
319: *bp++ = nextc;
320: adv();
321: }
322: *bp= '\0';
323: return savestr(buffer);
324: }
325:
326: Hidden int getnumber()
327: {
328: int base= (nextc == '0') ? 8 : 10;
329: int i= 0;
330: int d;
331:
332: for (;; adv()) {
333: d= nextc-'0';
334: if (d < 0 || d > 9)
335: break;
336: if (d > base) {
337: err("8 or 9 in octal number", "");
338: return 0;
339: }
340: i= i*base + d;
341: }
342: return i;
343: }
344:
345: Hidden string getstring()
346: {
347: char buf[256]; /* Arbitrary limit */
348: char quote= nextc;
349: char c;
350: int len= 0;
351:
352: adv();
353: while (nextc != quote) {
354: if (nextc == '\n') {
355: err("closing string quote not found", "");
356: return NULL;
357: }
358: if (nextc != '\\') {
359: c= nextc;
360: adv();
361: }
362: else {
363: adv();
364: switch (nextc) {
365:
366: case 'r': c= '\r'; adv(); break;
367: case 'n': c= '\n'; adv(); break;
368: case 'b': c= '\b'; adv(); break;
369: case 't': c= '\t'; adv(); break;
370: case 'f': c= '\f'; adv(); break;
371:
372: case 'E':
373: case 'e': c= ESC; adv(); break;
374:
375: case '0': case '1': case '2': case '3':
376: case '4': case '5': case '6': case '7':
377: c= nextc-'0';
378: adv();
379: if (nextc >= '0' && nextc < '8') {
380: c= 8*c + nextc-'0';
381: adv();
382: if (nextc >= '0' && nextc < '8') {
383: c= 8*c + nextc-'0';
384: adv();
385: }
386: }
387: break;
388:
389: default: c=nextc; adv(); break;
390:
391: }
392: }
393: if (len >= sizeof buf) {
394: err("string too long", "");
395: return NULL;
396: }
397: buf[len++]= c;
398: }
399: adv();
400: buf[len]= '\0';
401: return savestr(buf);
402: }
403:
404: Hidden string getitem()
405: {
406: char buf[2];
407: string keyname;
408: int i;
409:
410: switch (nextc) {
411: case '"':
412: case '\'':
413: return getstring();
414: case '^':
415: adv();
416: if (isalpha(nextc) || index("@^_[]\\?", nextc)) {
417: if (nextc == '?')
418: buf[0]= '\177';
419: else
420: buf[0]= nextc & 037;
421: buf[1]= '\0';
422: adv();
423: return savestr(buf);
424: }
425: err("Invalid character after '^'", "");
426: return NULL;
427: default:
428: if (isdigit(nextc)) {
429: buf[0]= getnumber();
430: buf[1]= '\0';
431: return savestr(buf);
432: }
433: if (isalpha(nextc) || nextc == '_') {
434: keyname= getname(); /* Cannot fail */
435: if (strlen(keyname) == 1)
436: return savestr(keyname);
437: /* Single letters stand for themselves */
438: i= lookup(keyname);
439: if (i < 0 || deftab[i].code <= 0) {
440: err("%s: not a key name", keyname);
441: freemem(keyname);
442: return NULL;
443: }
444: else if (deftab[i].def == NULL) {
445: err("%s: undefined key", keyname);
446: freemem(keyname);
447: return NULL;
448: }
449: else
450: return savestr(deftab[i].def);
451: }
452: err("Invalid item", "");
453: return NULL;
454: }
455: }
456:
457: Hidden string getrhs()
458: {
459: string first, item;
460:
461: skipsp();
462: first= getitem();
463: if (first != NULL) {
464: for (;;) {
465: skipsp();
466: if (nextc == '\n' || nextc == COMMENT)
467: break;
468: item= getitem();
469: if (item == NULL) {
470: freemem(first);
471: return NULL;
472: }
473: append(&first, item);
474: freemem(item);
475: }
476: }
477: return first;
478: }
479:
480: Hidden Procedure getdef()
481: {
482: string name;
483: int key;
484: string rhs;
485:
486: name= getname();
487: if (name == NULL)
488: return;
489: skipsp();
490: if (nextc != '=') {
491: err("Command name %s not followed by '='", name);
492: return;
493: }
494: key= lookup(name);
495: if (key < 0) {
496: err("Unknown command: %s", name);
497: return;
498: }
499: if (deftab[key].code < 0) {
500: err("No redefinition of %s allowed", name);
501: return;
502: }
503: adv();
504: rhs= getrhs();
505: if (rhs != NULL)
506: store(deftab[key].code, name, rhs);
507: }
508:
509: Hidden Procedure getline()
510: {
511: adv();
512: skipsp();
513: if (nextc != COMMENT && nextc != '\n')
514: getdef();
515: while (nextc != '\n')
516: adv();
517: }
518:
519: #ifndef NDEBUG
520: Hidden Procedure dump(where)
521: string where;
522: {
523: int i;
524: string s;
525:
526: printf("\nDump of key definitions %s.\n\n", where);
527: printf("Code Name Definition\n");
528: for (i= 0; i < ndefs; ++i) {
529: printf("%04o ", deftab[i].code);
530: if (deftab[i].name != NULL)
531: printf("%-15s ", deftab[i].name);
532: else
533: printf("%16s", "");
534: s= deftab[i].def;
535: if (s != NULL) {
536: for (; *s != '\0'; ++s) {
537: if (isascii(*s) && (isprint(*s) || *s == ' '))
538: fputc(*s, stdout);
539: else
540: printf("\\%03o", *s&0377);
541: }
542: }
543: printf("\n");
544: }
545: fflush(stdout);
546: }
547: #endif !NDEBUG
548:
549: Hidden Procedure countdefs()
550: {
551: struct tabent *d;
552:
553: d= deftab;
554: while (d->name != NULL || d->code != 0 || d->def != NULL) {
555: ++d;
556: if (d >= deftab+MAXDEFS)
557: syserr("too many predefined keys");
558: }
559: ndefs= d-deftab;
560: }
561:
562: Hidden Procedure process()
563: {
564: errcount= 0;
565: lcount= 1;
566: eof= No;
567: do {
568: getline();
569: } while (!eof);
570: }
571:
572: Hidden bool try(dir, file, type)
573: string dir, file, type;
574: {
575: char buffer[200];
576:
577: #ifdef IBMPC
578: sprintf(buffer, "%.150s\\%.9s%.3s", dir, file, type);
579: #else !IBMPC
580: sprintf(buffer, "%.150s/%.20s%.20s", dir, file, type);
581: #endif !IBMPC
582: fp= fopen(buffer, "r");
583: if (fp == NULL)
584: return No;
585: filename= buffer;
586: process();
587: fclose(fp);
588: #ifndef NDEBUG
589: if (dflag)
590: dump("after try");
591: #endif NDEBUG
592: return Yes;
593: }
594:
595: #ifndef IBMPC
596: Hidden Procedure readtermcap()
597: {
598: string tgetstr();
599: char buffer[1024]; /* Constant dictated by termcap manual entry */
600: static char area[1024];
601: string endarea= area;
602: string anentry;
603: struct tabent *d, *last;
604:
605: switch (tgetent(buffer, getenv("TERM"))) {
606:
607: default:
608: fprintf(stderr, "*** Bad tgetent() return value.\n");
609: /* Fall through */
610: case -1:
611: fprintf(stderr, "*** Can't read termcap.\n");
612: /* Fall through again */
613: case 0:
614: fprintf(stderr, "*** No description for your terminal.\n");
615: exit(1);
616:
617: case 1:
618: break;
619: }
620: last= deftab+ndefs;
621: for (d= deftab; d < last; ++d) {
622: if (d->def != NULL && d->def[0] == '=') {
623: anentry= tgetstr(d->def+1, &endarea);
624: if (anentry != NULL && anentry[0] != '\0')
625: d->def= anentry;
626: else
627: d->def= NULL;
628: }
629: }
630: }
631: #endif !IBMPC
632:
633: Visible Procedure initkeys()
634: {
635: string term= NULL;
636:
637: countdefs();
638: #ifndef NDEBUG
639: if (dflag)
640: dump("before termcap");
641: #endif NDEBUG
642: #ifndef IBMPC
643: readtermcap();
644: #ifndef NDEBUG
645: if (dflag)
646: dump("after termcap");
647: #endif NDEBUG
648: term= getenv("TERM");
649: if (term != NULL && term[0] == '\0')
650: term= NULL;
651: #endif !IBMPC
652: #ifdef DEBUG
653: /* Try in the current directory. Only for debugging porpoises. */
654: if (term != NULL)
655: if (try(".", keyfile, term)) return;
656: #endif DEBUG
657: if (term != NULL) {
658: if (try(homedir, keyfile, term)) return;
659: if (try(libdir, keyfile, term)) return;
660: }
661: #ifdef DEBUG
662: if (try(".", keyfile, deftype)) return;
663: #endif DEBUG
664: if (try(homedir, keyfile, deftype)) return;
665: if (try(libdir, keyfile, deftype)) return;
666: #ifndef NDEBUG
667: printf("[No key definitions file found, using defaults.]\n");
668: #endif !NDEBUG
669: }
670:
671:
672: /* Output a named string to the terminal */
673:
674: Hidden Procedure outstring(name)
675: string name;
676: {
677: int i= lookup(name);
678: string def;
679:
680: if (i >= 0 && (def= deftab[i].def) != NULL)
681: fputs(def, stdout);
682: }
683:
684:
685: /* Output the terminal's initialization sequence, if any. */
686:
687: Visible Procedure
688: initgetc()
689: {
690: outstring("term_init");
691: }
692:
693:
694: /* Output a sequence, if any, to return the terminal to a 'normal' state. */
695:
696: Visible Procedure endgetc()
697: {
698: outstring("term_done");
699: }
700:
701:
702: /* Read a command from the keyboard, decoding composite key definitions. */
703:
704: #ifndef IBMPC
705: /* Strip high bit from input characters (matters only on PWB systems?) */
706: #define getch() (getchar() & 0177)
707: #endif !IBMPC
708:
709: Visible int inchar()
710: {
711: int c;
712: struct tabent *d, *last;
713: char buffer[100];
714: int len;
715:
716: c= getch();
717: if (c == EOF)
718: return c;
719: #ifdef IBMPC
720: if (c == 0)
721: c= 0377;
722: #endif IBMPC
723: last= deftab+ndefs;
724: for (d= deftab; d < last; ++d) {
725: if (d->code > 0 && d->def != NULL && c == (d->def[0] & 0377))
726: break;
727: }
728: if (d == last) {
729: if (c == ESC) {
730: /* Kludge to make ESC-char by default equal to
731: char|MASK -- the command definitions do the rest:
732: e.g. WIDEN is 'w'|MASK, so ESC-w means WIDEN. */
733: c= getch();
734: if (c == EOF)
735: return EOF;
736: return (c&0177) | MASK;
737: }
738: return c;
739: }
740: if (d->def[1] == '\0')
741: return d->code;
742: buffer[0]= c;
743: len= 1;
744: for (;;) {
745: c= getch();
746: if (c == EOF)
747: return EOF;
748: buffer[len]= c;
749: if (len < sizeof buffer - 1)
750: ++len;
751: for (d= deftab; d < last; ++d) {
752: if (d->code > 0 && d->def != NULL
753: && strncmp(buffer, d->def, len) == 0)
754: break;
755: }
756: if (d == last) {
757: if (buffer[0] == ESC && len == 2) {
758: /* Same kludge as above */
759: return c&0177 | MASK;
760: }
761: return 0377; /* Hope this rings a bell */
762: }
763: if (d->def[len] == '\0')
764: return d->code;
765: }
766: }