/* Copyright (c) Stichting Mathematisch Centrum, Amsterdam, 1985. */ /* $Header: getc.c,v 2.5 85/08/22 16:02:44 timo Exp $ */ /* B editor -- read key definitions from file */ #include "b.h" #include "feat.h" #ifdef LINDA #include "b1mem.h" #define syserr EDsyserr #else !LINDA #define freemem(p) free(p) #endif !LINDA #include "file.h" #include "keys.h" #include extern bool dflag; #define ESC '\033' /* This file contains a little parser for key definition files. To allow sufficient freedom in preparing such a file, a simple grammar has been defined according to which the file is parsed. The parsing process is extremely simple, as it can be done top-down using recursive descent. Lexical conventions: - Blanks between lexical symbols are gnored. - From '#' to end of line is comment (except inside strings). - Strings are delimited by single or double quotes and use the same escape sequences as C strings, plus: \e or \E means an ESCape ('\033'). - Command names are like C identifiers ([a-zA-Z_][a-zA-Z0-9_]*). Upper/lower case distinction is significant. - numbers are octal or decimal integers in C-style (leading zero means octal) - After '^' a character is expected, this must be a letter or one of @^_[]\ . Syntax in modified BNF ([] mean 0 or 1, * means 0 or more, + means 1 or more): file: line* line: [def] [comment] def: commandname '=' rhs rhs: item+ item: string | '^' character | number Notes: - A definition for command "term_init" defines a string to be sent TO the terminal at initialization time, e.g. to set programmable function key definitions. Similar for "term_done" on exiting. - Command names are conventional editor commands. */ #ifndef LINDA /* Defines subroutine that used to be in the support levels: */ Hidden string getmem(nbytes) unsigned nbytes; { string malloc(); string pointer= malloc(nbytes); if (pointer == NULL) syserr("memory full in initkeys"); return pointer; } Hidden string regetmem(pp, nbytes) string *pp; unsigned nbytes; { *pp= realloc(*pp, nbytes); if (*pp == NULL) syserr("memory full in initkeys (regetmem)"); } #endif !LINDA #define COMMENT '#' /* Not B-like but very UNIX-like */ #define MAXDEFS 100 Hidden FILE *fp; /* File from which to read */ Hidden string filename; /* File name for error messages */ Hidden char nextc; /* Next character to be analyzed */ Hidden bool eof; /* EOF seen? */ Hidden int lcount; /* Current line number */ Hidden bool errcount; /* Number of errors detected */ struct tabent { int code; string name; string def; }; /* Table of key definitions, mostly filled by reading definitions from a file. The "I" macro has two arguments: the default for termcap and that for the IBM PC. It expands to either depending on whether IBMPC is defined. 'def' fields initialized with a string starting with '=' are termcap names, and are replaced by the corresponding termcap entry (NULL if none). On the IBM PC, 'extended codes' are by convention a null character followed by another character (usually the scan code). Since the null character is rather unsuitable for use in C strings, we use \377 (hex FF) instead, a code which has no assigned graphic is the extended IBM PC character set. E.g., F1 is 0-59, which we encode as \377\073 (since \073 is octal for 59 decimal). For the exact codes, see for instance the BASIC 2.0 manual, appendix G, or the XT Technical Reference, page 2-14. */ #ifdef IBMPC #define I(tc, ibm) ibm #else !IBMPC #define I(tc, ibm) tc #endif !IBMPC Visible struct tabent deftab[MAXDEFS] = { /* General rule: unix => ctrl-x IBM => alt-x where x is first letter of command name */ {0377, "ignore", NULL}, /* Entry to ignore a key */ {COPY, "copy", I(NULL, "\377\056")}, {DELETE, "delete", I(NULL, "\377\040")}, {DELETE, "delete", I(NULL, "\377\123")}, /* IBM DEL key */ {ACCEPT, "accept", I(NULL, "\377\022")}, /* ^E, alt-E */ {ACCEPT, "end", I(NULL, "\377\117")}, /* IBM END key */ {'\t', "tab", NULL}, /* = ACCEPT in Bed, insert tab in Linda */ {UNDO, "undo"}, /* Always backspace = ^H */ {REDRAW, "redraw", I(NULL, "\377\046")}, /* ^L, alt-L */ {REDRAW, "look"}, {RETURN, "newline"}, /* Always ^M */ {REDO, "redo", I(NULL, "\177")}, /* IBM ctrl-BS = ASCII 177 (DEL) */ {EXIT, "exit", I(NULL, "\377\055")}, /* ^X, alt-X */ #ifdef RECORDING /* * The IBM-PC has a problem here in ANSI.SYS mode: ctrl-P is * unusable because it means Print Screen, and alt-R is unusable * because it transmits 0, 19 but 19 is ctrl-S which means stop * output :-(. * The only reasonable place to put the things would then be on * function keys. You should do this in the key definitions file. (?) */ {PLAYBACK, "play", I(NULL, "\377\031")}, {PLAYBACK, "playback", I(NULL, "\377\031")}, {RECORD, "record", I(NULL, "\377\023")}, #endif RECORDING #ifdef LINDA {BFIND, "bfind", I(NULL, "\377\060")}, {FIND, "find", I(NULL, "\377\041")}, {GLOBAL, "global", I(NULL, "\377\042")}, {JOIN, "join", I(NULL, "\377\044")}, {TOGGLE, "toggle", I(NULL, "\377\024")}, {YANK, "yank", I(NULL, "\377\025")}, {LITERAL, "literal", I(NULL, "\377\057")}, /* ^V, alt-V */ #endif LINDA {WIDEN, "widen", I("=k1", "\377\073")}, /* IBM F1 */ {NARROW, "narrow", I("=k2", "\377\075")}, /* IBM F3 (!!!) */ {NARROW, "first"}, {RNARROW, "rnarrow", I("=k3", "\377\076")}, /* IBM F4 (!!!) */ {RNARROW, "last"}, {EXTEND, "extend", I("=k4", "\377\074")}, /* IBM F2 (!!!) */ {UPARROW, "up", I("=ku", "\377\110")}, {UPLINE, "upline", I("=k5", "\377\110")}, {LEFTARROW, "left", I("=kl", "\377\113")}, {PREVIOUS, "previous", I("=k6", NULL)}, {RITEARROW, "right", I("=kr", "\377\115")}, {NEXT, "next", I("=k7", NULL)}, {DOWNARROW, "down", I("=kd", "\377\120")}, {DOWNLINE, "downline", I("=k8", "\377\120")}, {GOTO, "goto", I("\033g", NULL)}, /* Doesn't exist on IBM */ #ifdef HELPFUL {HELP, "help", I("\033?", "\377\104")}, /* ESC ?, IBM F10 */ #endif HELPFUL {0, "term_init", I("=ks", NULL)}, {0, "term_done", I("=ke", NULL)}, }; #undef I Hidden int ndefs; Hidden Procedure err(fmt, arg) string fmt, arg; { if (errcount == 0) fprintf(stderr, "Errors in key definitions file:\n"); ++errcount; fprintf(stderr, "%s, line %d: ", filename, lcount); fprintf(stderr, fmt, arg); fprintf(stderr, "\n"); } Hidden Procedure adv() { int c; if (eof) return; c= getc(fp); if (c == EOF) { nextc= '\n'; eof= Yes; } else { nextc= c; if (c == '\n') ++lcount; } } Hidden Procedure skipsp() { while (nextc == ' ' || nextc == '\t') adv(); } Hidden int lookup(name) string name; { int i; for (i= 0; i < ndefs; ++i) { if (deftab[i].name != NULL && strcmp(name, deftab[i].name) == 0) return i; } return -1; } Hidden Procedure store(code, name, def) int code; string name; string def; { struct tabent *d, *last= deftab+ndefs; string p, q; /* Undefine conflicting definitions. Conflicts arise when a command definition is an initial subsequence of another, or vice versa. Key definitions (code < 0) are not undefined. */ if (code > 0) { for (d= deftab; d < last; ++d) { if (d->code >= 0 && d->def != NULL) { for (p= def, q= d->def; *p == *q; ++p, ++q) { if (*p == '\0' || *q == '\0') { d->def= NULL; break; } } } } } /* Find a free slot with the same code and NULL definition */ /* (For code == 0, the name must match instead of the code, and the definition need not be NULL) */ for (d= deftab; d < last; ++d) { if (code == 0 ? strcmp(name, d->name) == 0 : (d->code == code && d->def == NULL)) break; } if (d == last) { /* Extend definition table */ if (ndefs >= MAXDEFS) { err("Too many key definitions", ""); return; } ++ndefs; d->code= code; d->name= name; } d->def= def; } Hidden string savestr(s) string s; { string new; new= getmem((unsigned) (strlen(s) + 1)); strcpy(new, s); return new; } Hidden Procedure append(to, item) string *to, item; { int len= strlen(*to) + strlen(item) + 1; regetmem(to, len); strcat(*to, item); } Hidden string getname() { char buffer[20]; string bp; if (!isalpha(nextc) && nextc != '_') { err("No name where expected", ""); return NULL; } for (bp= buffer; isalnum(nextc) || nextc == '_'; ) { if (bp < buffer + sizeof buffer - 1) *bp++ = nextc; adv(); } *bp= '\0'; return savestr(buffer); } Hidden int getnumber() { int base= (nextc == '0') ? 8 : 10; int i= 0; int d; for (;; adv()) { d= nextc-'0'; if (d < 0 || d > 9) break; if (d > base) { err("8 or 9 in octal number", ""); return 0; } i= i*base + d; } return i; } Hidden string getstring() { char buf[256]; /* Arbitrary limit */ char quote= nextc; char c; int len= 0; adv(); while (nextc != quote) { if (nextc == '\n') { err("closing string quote not found", ""); return NULL; } if (nextc != '\\') { c= nextc; adv(); } else { adv(); switch (nextc) { case 'r': c= '\r'; adv(); break; case 'n': c= '\n'; adv(); break; case 'b': c= '\b'; adv(); break; case 't': c= '\t'; adv(); break; case 'f': c= '\f'; adv(); break; case 'E': case 'e': c= ESC; adv(); break; case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': c= nextc-'0'; adv(); if (nextc >= '0' && nextc < '8') { c= 8*c + nextc-'0'; adv(); if (nextc >= '0' && nextc < '8') { c= 8*c + nextc-'0'; adv(); } } break; default: c=nextc; adv(); break; } } if (len >= sizeof buf) { err("string too long", ""); return NULL; } buf[len++]= c; } adv(); buf[len]= '\0'; return savestr(buf); } Hidden string getitem() { char buf[2]; string keyname; int i; switch (nextc) { case '"': case '\'': return getstring(); case '^': adv(); if (isalpha(nextc) || index("@^_[]\\?", nextc)) { if (nextc == '?') buf[0]= '\177'; else buf[0]= nextc & 037; buf[1]= '\0'; adv(); return savestr(buf); } err("Invalid character after '^'", ""); return NULL; default: if (isdigit(nextc)) { buf[0]= getnumber(); buf[1]= '\0'; return savestr(buf); } if (isalpha(nextc) || nextc == '_') { keyname= getname(); /* Cannot fail */ if (strlen(keyname) == 1) return savestr(keyname); /* Single letters stand for themselves */ i= lookup(keyname); if (i < 0 || deftab[i].code <= 0) { err("%s: not a key name", keyname); freemem(keyname); return NULL; } else if (deftab[i].def == NULL) { err("%s: undefined key", keyname); freemem(keyname); return NULL; } else return savestr(deftab[i].def); } err("Invalid item", ""); return NULL; } } Hidden string getrhs() { string first, item; skipsp(); first= getitem(); if (first != NULL) { for (;;) { skipsp(); if (nextc == '\n' || nextc == COMMENT) break; item= getitem(); if (item == NULL) { freemem(first); return NULL; } append(&first, item); freemem(item); } } return first; } Hidden Procedure getdef() { string name; int key; string rhs; name= getname(); if (name == NULL) return; skipsp(); if (nextc != '=') { err("Command name %s not followed by '='", name); return; } key= lookup(name); if (key < 0) { err("Unknown command: %s", name); return; } if (deftab[key].code < 0) { err("No redefinition of %s allowed", name); return; } adv(); rhs= getrhs(); if (rhs != NULL) store(deftab[key].code, name, rhs); } Hidden Procedure getline() { adv(); skipsp(); if (nextc != COMMENT && nextc != '\n') getdef(); while (nextc != '\n') adv(); } #ifndef NDEBUG Hidden Procedure dump(where) string where; { int i; string s; printf("\nDump of key definitions %s.\n\n", where); printf("Code Name Definition\n"); for (i= 0; i < ndefs; ++i) { printf("%04o ", deftab[i].code); if (deftab[i].name != NULL) printf("%-15s ", deftab[i].name); else printf("%16s", ""); s= deftab[i].def; if (s != NULL) { for (; *s != '\0'; ++s) { if (isascii(*s) && (isprint(*s) || *s == ' ')) fputc(*s, stdout); else printf("\\%03o", *s&0377); } } printf("\n"); } fflush(stdout); } #endif !NDEBUG Hidden Procedure countdefs() { struct tabent *d; d= deftab; while (d->name != NULL || d->code != 0 || d->def != NULL) { ++d; if (d >= deftab+MAXDEFS) syserr("too many predefined keys"); } ndefs= d-deftab; } Hidden Procedure process() { errcount= 0; lcount= 1; eof= No; do { getline(); } while (!eof); } Hidden bool try(dir, file, type) string dir, file, type; { char buffer[200]; #ifdef IBMPC sprintf(buffer, "%.150s\\%.9s%.3s", dir, file, type); #else !IBMPC sprintf(buffer, "%.150s/%.20s%.20s", dir, file, type); #endif !IBMPC fp= fopen(buffer, "r"); if (fp == NULL) return No; filename= buffer; process(); fclose(fp); #ifndef NDEBUG if (dflag) dump("after try"); #endif NDEBUG return Yes; } #ifndef IBMPC Hidden Procedure readtermcap() { string tgetstr(); char buffer[1024]; /* Constant dictated by termcap manual entry */ static char area[1024]; string endarea= area; string anentry; struct tabent *d, *last; switch (tgetent(buffer, getenv("TERM"))) { default: fprintf(stderr, "*** Bad tgetent() return value.\n"); /* Fall through */ case -1: fprintf(stderr, "*** Can't read termcap.\n"); /* Fall through again */ case 0: fprintf(stderr, "*** No description for your terminal.\n"); exit(1); case 1: break; } last= deftab+ndefs; for (d= deftab; d < last; ++d) { if (d->def != NULL && d->def[0] == '=') { anentry= tgetstr(d->def+1, &endarea); if (anentry != NULL && anentry[0] != '\0') d->def= anentry; else d->def= NULL; } } } #endif !IBMPC Visible Procedure initkeys() { string term= NULL; countdefs(); #ifndef NDEBUG if (dflag) dump("before termcap"); #endif NDEBUG #ifndef IBMPC readtermcap(); #ifndef NDEBUG if (dflag) dump("after termcap"); #endif NDEBUG term= getenv("TERM"); if (term != NULL && term[0] == '\0') term= NULL; #endif !IBMPC #ifdef DEBUG /* Try in the current directory. Only for debugging porpoises. */ if (term != NULL) if (try(".", keyfile, term)) return; #endif DEBUG if (term != NULL) { if (try(homedir, keyfile, term)) return; if (try(libdir, keyfile, term)) return; } #ifdef DEBUG if (try(".", keyfile, deftype)) return; #endif DEBUG if (try(homedir, keyfile, deftype)) return; if (try(libdir, keyfile, deftype)) return; #ifndef NDEBUG printf("[No key definitions file found, using defaults.]\n"); #endif !NDEBUG } /* Output a named string to the terminal */ Hidden Procedure outstring(name) string name; { int i= lookup(name); string def; if (i >= 0 && (def= deftab[i].def) != NULL) fputs(def, stdout); } /* Output the terminal's initialization sequence, if any. */ Visible Procedure initgetc() { outstring("term_init"); } /* Output a sequence, if any, to return the terminal to a 'normal' state. */ Visible Procedure endgetc() { outstring("term_done"); } /* Read a command from the keyboard, decoding composite key definitions. */ #ifndef IBMPC /* Strip high bit from input characters (matters only on PWB systems?) */ #define getch() (getchar() & 0177) #endif !IBMPC Visible int inchar() { int c; struct tabent *d, *last; char buffer[100]; int len; c= getch(); if (c == EOF) return c; #ifdef IBMPC if (c == 0) c= 0377; #endif IBMPC last= deftab+ndefs; for (d= deftab; d < last; ++d) { if (d->code > 0 && d->def != NULL && c == (d->def[0] & 0377)) break; } if (d == last) { if (c == ESC) { /* Kludge to make ESC-char by default equal to char|MASK -- the command definitions do the rest: e.g. WIDEN is 'w'|MASK, so ESC-w means WIDEN. */ c= getch(); if (c == EOF) return EOF; return (c&0177) | MASK; } return c; } if (d->def[1] == '\0') return d->code; buffer[0]= c; len= 1; for (;;) { c= getch(); if (c == EOF) return EOF; buffer[len]= c; if (len < sizeof buffer - 1) ++len; for (d= deftab; d < last; ++d) { if (d->code > 0 && d->def != NULL && strncmp(buffer, d->def, len) == 0) break; } if (d == last) { if (buffer[0] == ESC && len == 2) { /* Same kludge as above */ return c&0177 | MASK; } return 0377; /* Hope this rings a bell */ } if (d->def[len] == '\0') return d->code; } }