/*
* webster - look words up in the dictionary
*
* This program connects to the Webster server to get definitions of words.
* Words may be given on the command line, or, if no arguments are given,
* the program runs interactively. If -s or -d is given, it puts the program
* in "spell" or "define" mode, respectively. "define" is the default.
*
* In either mode, a word may include the wildcard characters '%' and '*'.
* The '%' character matches exactly one character, while the '*' matches
* zero or more characters. If wildcards are used, the program will
* return either "No match" or a list of matching words. "!word" looks the
* word up in the alternate mode.
*
* In interactive mode only, Tenex-style command completion may also be
* used. Typing a '?' following part of a word will cause the program
* to print all words which begin with the partial word, or the program
* will beep if nothing matches. Typing an ESCape character causes the
* program to attempt to complete the word. If the word can be completed,
* the new word is printed; otherwise, the program beeps. Wildcards
* may be used to specify the partial words.
*
* David A. Curry
* Purdue University
* davy@ee.purdue.edu
* April, 1986
*/
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <signal.h>
#include <sys/time.h>
#include <sys/times.h>
#include <sgtty.h>
#include <netdb.h>
#include <ctype.h>
#include <stdio.h>
#include <sys/un.h>
#include <arpa/inet.h>
#include "lint.h"
#include "interpret.h"
#include "config.h"
#include "comm.h"
#include "object.h"
#include <errno.h>
#include "webster.h"
#define BACKSPACE 010 /* backspace character ^H */
#define WORDERASE 027 /* word erase character ^W */
#define LINEERASE 030 /* line kill character ^X */
#define LINERTYPE 022 /* line reprint character ^R */
#define COMPLETE 033 /* word completion character ^[ */
#define ENDINGS '?' /* print matches character */
#define ALTMODE '!' /* use other lookup mode */
#define THESMODE '#' /* lookup in thesaurus (sah) */
#define DEFINE 0 /* DEFINE mode */
#define SPELL 1 /* SPELL mode */
#define THESAURUS 2 /* THESAURUS mode */
struct sgttyb sgttyb; /* tty modes when interactive */
struct sgttyb rsgttyb; /* original tty modes */
extern int errno;
/*
* added at IU
* ..sahayman 89/02/28
*/
struct tchars tchars;
struct ltchars ltchars;
int connectup(), socket(), connet(), byebye(), endings(), strlen(), send();
int strncmp(), connect(), getline();
void setitimer(), bzero(), define(), spell(), bcopy(), complete(), help();
void define(), listlines(), putline();
char *strcpy();
int mode = DEFINE;
FILE *WebsterSock; /* for reading from the server */
int interactive = 0; /* 1 when running interactive */
int full_search = 0;
char *pager = NULL;
char *getenv();
extern struct object *command_giver;
struct object c_g;
void
add_message2(fmt, a1, a2, a3, a4, a5, a6, a7, a8, a9)
char *fmt;
int a1, a2, a3, a4, a5, a6, a7, a8, a9;
{
if (c_g.flags & O_DESTRUCTED) return;
/* add_output(c_g.interactive, 0, fmt, a1, a2, a3, a4, a5, a6,
a7, a8, a9); */
add_message(fmt, a1, a2, a3, a4, a5, a6, a7, a8, a9);
}
int webster(arg1, arg2)
char *arg1, *arg2;
{
int didone = 0;
struct itimerval value;
void go_out();
char aa[100], bb[100];
mode = DEFINE;
/*
* Connect to the server.
*/
if (!connectup()) return 0;
c_g = *command_giver;
value.it_value.tv_sec = 10; /* 10 seconds, no more! */
value.it_interval.tv_sec =10; /* Kickaha! */
setitimer(ITIMER_PROF, &value, NULL);
signal(SIGPROF, go_out);
bzero(aa, 100);
bzero(bb, 100);
if (arg1) sscanf(arg1, "%s %s", aa, bb);
if (bb[0] == (char)0) strcpy(bb, arg1);
/*
* If we were given command line arguments, just
* try to define each word.
*/
if (bb != (char *)0) {
if (*aa == '-') {
switch (*(aa+1)) {
case 'd':
mode = DEFINE;
break;
case 's':
mode = SPELL;
break;
case 't':
mode = THESAURUS;
break;
case 'f':
full_search++;
break;
default:
add_message2("Usage: webster [-d] [-s] [-t] [-f] [word]\n");
value.it_value.tv_sec = 60*5; /* 5 minutes must be enough */
value.it_interval.tv_sec = 3600; /* Kickaha ! */
setitimer(ITIMER_PROF, &value, NULL);
return 1;
}
}
/*
* Look up the word.
*/
if (mode == DEFINE || mode == THESAURUS)
define(bb, mode);
else
spell(bb);
didone++;
if (didone)
{
value.it_value.tv_sec = 60*5; /* 5 minutes must be enough */
value.it_interval.tv_sec = 3600; /* Kickaha ! */
setitimer(ITIMER_PROF, &value, NULL);
return 0;
}
}
/*
* If no arguments were given, set up the
* terminal modes and run interactively.
*/
/* setup();
interact(); */
value.it_value.tv_sec = 60*5; /* 5 minutes must be enough */
value.it_interval.tv_sec = 3600; /* Kickaha ! */
setitimer(ITIMER_PROF, &value, NULL);
add_message2("No word specified!\n");
return 0;
}
void go_out()
{
struct itimerval value;
value.it_value.tv_sec = 60*5; /* 5 minutes must be enough */
value.it_interval.tv_sec = 3600; /* Kickaha ! */
setitimer(ITIMER_PROF, &value, NULL);
signal(SIGPROF, SIG_DFL);
add_message2("Couldn't reach server host!\n");
}
/*
* connectup - connects to the Webster server.
*/
int connectup()
{
int s;
struct sockaddr_in sin;
struct hostent *hp;
struct servent *sp;
struct hostent *gethostbyname();
struct servent *getservbyname();
extern int byebye();
char *whichhost, *getenv();
whichhost = getenv("WEBSTERHOST");
if ( whichhost == NULL )
whichhost = WEBSTERHOST;
/*
* Look up the host in the host file.
*/
if ((hp = gethostbyname(whichhost)) == NULL) {
add_message2("webster: %s: unknown host.\n", whichhost);
return 0;
}
bzero(&sin, sizeof(struct sockaddr_in));
/*
* Build the server's address.
*/
sin.sin_family = AF_INET;
bcopy(hp->h_addr, &sin.sin_addr, hp->h_length);
if ((sp = getservbyname(WEBSTERNAME, "tcp")) == NULL)
sin.sin_port = htons(WEBSTERPORT);
else
sin.sin_port = sp->s_port;
/*
* Get a TCP socket.
*/
if ((s = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
/* perror("webster: socket"); */
add_message2("Webster couldn't get TCP socket.\n");
return 0;
}
/*
* Try to connect.
*/
/* signal(SIGINT, byebye); */
if (connect(s, &sin, sizeof(struct sockaddr_in)) < 0) {
add_message2("Webster: host %s: ", whichhost);
/* perror("connect"); */
add_message2("couldn't connect (try again).\n");
return 0;
}
/* signal(SIGINT, SIG_DFL); */
/*
* Open the socket for stdio.
*/
WebsterSock = fdopen(s, "r");
return 1;
}
/*
* setup - turns on CBREAK, turns off ECHO. Also trap signals.
*/
void setup()
{
extern int byebye();
extern int suspend();
interactive = 1;
/* ioctl(0, TIOCGETP, &sgttyb);
ioctl(0, TIOCGETC, &tchars);
ioctl(0, TIOCGLTC, <chars);
rsgttyb = sgttyb;
signal(SIGINT, byebye);
signal(SIGQUIT, byebye);
signal(SIGTSTP, suspend);
sgttyb.sg_flags |= CBREAK;
sgttyb.sg_flags &= ~ECHO;
ioctl(0, TIOCSETP, &sgttyb); */
}
/*
* interact - interact with the user.
*/
void interact()
{
/*
int c;
*/
char c;
char buf[1024];
char *s, *t;
/*
* Forever...
*/
for (;;) {
/*
* Prompt for a word.
*/
s = buf;
write(1, "Word: ", 6);
/*
* Forever... read characters. We
* break out of this from inside.
*/
for (;;) {
/*
if ( (c = getchar()) < 0 )
*/
if (read(0, &c, 1) <= 0)
byebye();
/*
* Added at IU - use the user's own backspace,
* and line erase characters. Check for EOF too.
*
* Could also do worderase here if really keen
* but would require looking up ltchars
* and I'm lazy, and we already have sgttyb sitting
* around... Should also probably handle EOF here.
* ..sahayman 89/02/28
*/
if ( c == sgttyb.sg_erase )
c = BACKSPACE;
else if ( c == sgttyb.sg_kill )
c = LINEERASE;
else if ( c == ltchars.t_werasc )
c = WORDERASE;
else if ( c == ltchars.t_rprntc )
c = LINERTYPE;
else if ( c == tchars.t_brkc )
c = COMPLETE;
else if ( c == tchars.t_eofc )
byebye();
switch (c) {
case BACKSPACE:
/*
* If not at the beginning of a line,
* back up one character.
*/
if (s > buf) {
write(1, "\b \b", 3);
s--;
}
continue;
case WORDERASE:
/*
* Until we hit beginning of line
* or beginning of word, back up.
*/
while ((s > buf) && (*s != ' ')) {
write(1, "\b \b", 3);
s--;
}
continue;
case LINEERASE:
/*
* Until we hit beginning of line,
* back up.
*/
while (s > buf) {
write(1, "\b \b", 3);
s--;
}
continue;
case LINERTYPE:
/*
* Retype the line.
*/
write(1, "\r\nWord: ", 8);
for (t=buf; t < s; t++)
write(1, t, 1);
continue;
case COMPLETE:
/*
* Try to complete what they typed
* so far. Put the pointer at the
* end of the new word.
*/
*s = NULL;
complete(buf);
for (s=buf; *s; s++)
;
continue;
case ENDINGS:
/*
* If it's the first character,
* then print some help. Otherwise,
* try to find endings for the word.
* endings() returns 1 if no endings
* were found, 0 if some were found.
* This tells us whether to reprint
* the current word or not.
*/
if (s == buf) {
help();
}
else {
*s = NULL;
if (endings(buf) == 0) {
write(1, "Word: ", 6);
for (s=buf; *s; s++)
write(1, s, 1);
}
continue;
}
break;
case '\n':
/*
* If at the start of a word,
* newline is exit.
*/
if (s == buf)
byebye();
/*
* Otherwise, try to define
* the word.
*/
*s = NULL;
write(1, "\n", 1);
if (*buf == ALTMODE) {
if (strlen(buf) == 1)
break;
if (mode == DEFINE || mode == THESAURUS)
spell(buf+1);
else
define(buf+1,mode);
}
else if ( *buf == THESMODE ) {
int oldmode = mode;
if ( strlen(buf) == 1 )
break;
mode = THESAURUS;
define(buf+1,mode);
mode = oldmode;
} else {
if (mode == DEFINE || mode == THESAURUS)
define(buf,mode);
else
spell(buf);
}
fflush(stdout);
break;
default:
/*
* Echo the character and copy it.
*/
write(1, &c, 1);
*s++ = c;
continue;
}
break;
}
}
}
/*
* define - try to define a word and print its definition.
*/
void define(word, mode2)
char *word;
int mode2;
{
int c, refs;
char buf[1024];
/*
* first mode is the normal dictionary so that
* we don't send INDEX commands to an old server
*/
static int last_mode = DEFINE;
static int last_full = 0;
FILE *fpager, *popen();
/*
* Command is "DEFINE<space>word<nl>".
*/
/*
* Send appropriate INDEX instruction if anything has changed
* since last time.
*/
if ( mode2 != last_mode || full_search != last_full ) {
last_mode = mode2;
last_full = full_search;
if ( mode2 == DEFINE ) {
sprintf(buf, "INDEX %s\r\n",
full_search ? "dictionary-full" : "dictionary" );
} else if ( mode2 == THESAURUS ) {
sprintf(buf, "INDEX thesaurus\r\n");
}
if ( send(fileno(WebsterSock), buf, strlen(buf), 0) < 0 ) {
/* perror("webster: send"); */
add_message2("Webster couldn't send!\n");
byebye();
}
/*
getline(buf);
*/
}
sprintf(buf, "DEFINE %s\r\n", word);
/*
* Send the command.
*/
if (send(fileno(WebsterSock), buf, strlen(buf), 0) < 0) {
/* perror("webster: send"); */
add_message2("Webster couldn't send.\n");
byebye();
}
/*
* Read the first line back from the server. This
* line tells us what the result of our DEFINE
* request was.
*/
getline(buf);
/*
* "WILD<space>0<nl>" means they used wild cards and no
* matches were found.
*/
if (!strncmp(buf, "WILD 0", 6)) {
add_message2("No match.\n");
return;
}
/*
* "WILD<nl>" means that the wildcard matched, so we
* print a list of possible matches.
*/
if (!strncmp(buf, "WILD", 4)) {
add_message2("Possible matches are:\n");
/*
* List lines.
*/
listlines(0, 1);
add_message2("\n");
return;
}
/*
* "SPELLING<space>0<nl>" means the word is not defined,
* and there are no alternate spellings.
*/
if (!strncmp(buf, "SPELLING 0", 10)) {
add_message2("No %s",
mode2 == THESAURUS ? "thesaurus entry" : "definition ");
add_message2(" for '%s'.\n", word);
/*
* Everywhere else there's a blank line between words.
*/
add_message2("\n");
/* putchar('\n'); */
return;
}
/*
* "SPELLING<nl>" means the word is not defined, but
* some alternate spellings were found. Print
* them out.
*/
if (!strncmp(buf, "SPELLING", 8)) {
add_message2("No %s",
mode2 == THESAURUS ? "thesaurus entry" : "definition ");
add_message2(" for '%s'. Maybe you mean:\n", word);
/*
* List lines.
*/
listlines(0, 1);
/* putchar('\n'); */
add_message2("\n");
return;
}
/*
* "DEFINITION<space>n<nl>" means the word is defined,
* and there are n cross-references.
*/
if (!strncmp(buf, "DEFINITION", 10)) {
/*
* Use pager if desired
*/
if ( pager ) {
fpager = popen(pager, "w");
if ( fpager == NULL ) {
add_message2("Warning: pager program.\n");
/* perror(pager); */
fpager = stdout;
}
} else {
fpager = stdout;
}
sscanf(buf+11, "%d", &refs);
/*
* Print any cross references.
*/
if (refs > 0) {
add_message2("Cross references:\n");
/*
* List lines.
*/
listlines(refs, 1);
add_message2(" \n");
}
/*
* Print the definition.
*/
while ((c = getc(WebsterSock)) != EOF) {
if (c == EOFCH)
break;
c &= 0177;
add_message2("%c", c);
}
add_message2("\n");
if ( fpager != stdout )
pclose(fpager);
return;
}
/*
* An error message.
*/
if (!strncmp(buf, "ERROR ", 6)) {
if (!strncmp(buf+6, "FATAL", 5)) {
add_message2("%s\n", buf+11);
byebye();
}
else {
add_message2("%s\n", buf+17);
return;
}
}
/*
* Should never get here.
*/
while (((c = getc(WebsterSock)) != EOF) && (c != EOFCH))
;
}
/*
* spell - look up a word and see if it's spelled correctly.
*/
void spell(word)
char *word;
{
int c;
char buf[1024];
/*
* Command is "SPELL<space>word<nl>".
*/
sprintf(buf, "SPELL %s\r\n", word);
/*
* Send the command.
*/
if (send(fileno(WebsterSock), buf, strlen(buf), 0) < 0) {
/* perror("webster: send"); */
add_message2("Webster couldn't send!\n");
byebye();
}
/*
* Read the first line back from the server. This
* line tells us what the result of our SPELL
* request was.
*/
getline(buf);
/*
* "SPELLING<space>0<nl>" means the word is not spelled correctly,
* and there are no alternate spellings.
*/
if (!strncmp(buf, "SPELLING 0", 10)) {
add_message2("'%s' is not a correct spelling.\n", word);
return;
}
/*
* "SPELLING<space>1<nl>" means the word is spelled correctly.
*/
if (!strncmp(buf, "SPELLING 1", 10)) {
add_message2("'%s' is spelled correctly.\n", word);
return;
}
/*
* "SPELLING<nl>" means the word is not spelled correctly, but
* some alternate spellings were found. Print them out.
*/
if (!strncmp(buf, "SPELLING", 8)) {
add_message2("No spelling for '%s'. Maybe you mean:\n", word);
/*
* List lines.
*/
listlines(0, 1);
add_message2("\n");
/* putchar('\n'); */
return;
}
/*
* An error message.
*/
if (!strncmp(buf, "ERROR ", 6)) {
if (!strncmp(buf+6, "FATAL", 5)) {
add_message2("%s\n", buf+11);
byebye();
}
else {
add_message2("%s\n", buf+17);
return;
}
}
/*
* Should never get here.
*/
while (((c = getc(WebsterSock)) != EOF) && (c != EOFCH))
;
}
/*
* complete - try to complete the word.
*/
void complete(word)
char *word;
{
int c;
char buf[1024];
char *s;
/*
* Command is "COMPLETE<space>word<nl>".
*/
sprintf(buf, "COMPLETE %s\r\n", word);
/*
* Send the command.
*/
if (send(fileno(WebsterSock), buf, strlen(buf), 0) < 0) {
/* perror("webster: send"); */
add_message2("Webster couldn't send command.\n");
byebye();
}
/*
* Get the first line from the server, which tells
* us the reult of our request.
*/
getline(buf);
/*
* "AMBIGUOUS<space>n<nl>" means the word is ambiguous,
* with n possible matches. We ignore the n, and just
* beep.
*/
if (!strncmp(buf, "AMBIGUOUS", 9)) {
add_message2("");
/* write(1, "\007", 1); */
return;
}
/*
* "COMPLETION<space>full-word<nl>" means the
* word was completed. Erase what they typed
* and print the new word over it. This takes
* care of things if they used wildcards.
*/
if (!strncmp(buf, "COMPLETION", 10)) {
for (s=word; *s; s++)
{
add_message2("\b");
/* write(1, "\b", 1); */
}
s = buf+11;
while (((*s & 0177) != '\r') && ((*s & 0177) != '\n') &&
((*s & 0177) != NULL)) {
/* write(1, s, 1); */
add_message2("%c", s);
s++;
}
/*
* Put the new word back into word. This
* gets rid of the wildcards here.
*/
*s = NULL;
strcpy(word, buf+11);
return;
}
/*
* An error message.
*/
if (!strncmp(buf, "ERROR ", 6)) {
if (!strncmp(buf+6, "FATAL", 5)) {
add_message2("%s\n", buf+11);
byebye();
}
else {
add_message2("%s\n", buf+17);
return;
}
}
/*
* Should never get here.
*/
while (((c = getc(WebsterSock)) != EOF) && (c != EOFCH))
;
}
/*
* endings - find possible endings for a word.
*/
int endings(word)
char *word;
{
int c;
char buf[1024];
/*
* Command is "ENDINGS<space>word<nl>".
*/
sprintf(buf, "ENDINGS %s\r\n", word);
/*
* Send the command.
*/
if (send(fileno(WebsterSock), buf, strlen(buf), 0) < 0) {
/* perror("webster: send"); */
add_message2("Webster couldn't send command!\n");
byebye();
}
/*
* Get the first line from the server, which tells
* us the result of the search.
*/
getline(buf);
/*
* "MATCHS<space>0<nl>" means nothing matched,
* so we beep at them.
*/
if (!strncmp(buf, "MATCHS 0", 8)) {
/* write(1, "\007", 1); */
add_message2("");
return(1);
}
/*
* "MATCHS<nl>" means there were matches, so
* print them out.
*/
if (!strncmp(buf, "MATCHS", 6)) {
add_message2("\nMaybe you mean:\n");
/*
* List lines.
*/
listlines(0, 0);
add_message2("\n");
/* putchar('\n'); */
return(0);
}
/*
* An error message.
*/
if (!strncmp(buf, "ERROR ", 6)) {
if (!strncmp(buf+6, "FATAL", 5)) {
add_message2("%s\n", buf+11);
byebye();
}
else {
add_message2("%s\n", buf+17);
return(0);
}
}
/*
* Should never get here.
*/
while (((c = getc(WebsterSock)) != EOF) && (c != EOFCH))
;
return(0);
}
/*
* getline - read one line from the server and put it in s.
*/
int getline(s)
char *s;
{
int c;
/*
* Read in chars. If we hit EOFCH, return
* 0.
*/
while ((c = getc(WebsterSock)) != EOF) {
if (c == EOFCH)
return(0);
c &= 0177;
if (c == '\r')
continue;
if (c == '\n')
break;
*s++ = c;
}
*s = NULL;
return(1);
}
/*
* listlines - list WILD-style lines on the screen.
*/
void listlines(n, num)
int n;
int num;
{
char buf[1024];
int col;
add_message2(" ");
/*
* If n is non-zero, we only want to list n lines.
* Otherwise, we go till we hit EOFCH. Lines are
* printed in four columns.
*/
if (n) {
col = 0;
while (n-- > 0) {
getline(buf);
putline(buf, num);
if (++col == 3) {
add_message2("\n ");
col = 0;
}
}
}
else {
col = 0;
while (getline(buf) > 0) {
putline(buf, num);
if (++col == 3) {
add_message2("\n ");
col = 0;
}
}
}
if (col)
{
/* putchar('\n'); */
add_message2("\n");
}
}
/*
* putline - put out a line, if num is 0, skip the line number.
*/
void putline(buf, num)
char *buf;
int num;
{
int lnum;
char line[1024];
sscanf(buf, "%d %[^\n]", &lnum, line);
/* sscanf(line, "%s\n", line);
line[strlen(line)] = 0; */
if (num)
{
add_message2("%2d. ", lnum);
/* add_message2("%-22s", line); */
add_message2("%s", line);
}
else
{
/* add_message2("%-26s", line); */
add_message2("%s", line);
}
}
/*
* help - print a help message.
*/
void help()
{
add_message2("\n Type in the word you want defined, or a blank line to exit. Additionally,\n");
add_message2("Webster can match words using wildcards. The character '%%' in a word means\n");
add_message2("match exactly one character; while the character '*' means match zero or more\n");
add_message2("characters. Typing \"!word\" will check the spelling of the word instead\n");
add_message2("of checking its definition.\n");
add_message2(" Typing a partial word followed by '?' will print all the words in the\n");
add_message2("dictionary which match your partial word. Typing a partial word followed by an\n");
add_message2("ESCape character will try to complete the word for you. If the partial word\n");
add_message2("is ambiguous, Webster will beep at you. Note that you can use the wildcards\n");
add_message2("along with ESC and ?. For example (the underlined parts are typed by the\n");
add_message2("user, the rest by Webster),\n");
add_message2("\n");
add_message2("Word: balla? Maybe you mean:\n");
add_message2(" ------\n");
add_message2(" 1. ballad 2. ballade 3. baladry 4. ballast\n");
add_message2("Word: pluria<ESC>xial\n");
add_message2(" -----------\n");
add_message2("Word: plu*x<ESC>\n");
add_message2(" ----------\n");
add_message2("Word: pluriaxial\n");
add_message2("\n---- End of Help File ----\n\n");
}
/*
* byebye - called on exit.
*/
int byebye()
{
/*
* If interactive, reset the tty modes.
*/
/* if (interactive)
ioctl(0, TIOCSETP, &rsgttyb); */
/*
* Close the socket and exit.
*/
fclose(WebsterSock);
/* write(1, "\n", 1); */
add_message2("\n");
return 0;
}
/*
* suspend - reset tty modes and supend ourselves.
*/
void suspend2()
{
extern int suspend();
/*
* Reset tty modes and suspend.
*/
/* ioctl(0, TIOCSETP, &rsgttyb);
signal(SIGTSTP, SIG_DFL);
blocked = sigsetmask(0);
kill(0, SIGTSTP); */
/*
* We come here on SIGCONT. Reset
* the signal mask and tty modes.
*/
/* sigsetmask(blocked);
signal(SIGTSTP, suspend);
ioctl(0, TIOCSETP, &sgttyb); */
}