//Christopher Busch
//(c) 1993 all rights reserved.
//eliza.cpp
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <ctype.h>
#include <search.h>
#include <unistd.h>
#include "eliza.hpp"
#ifdef DMALLOC
# include "dmalloc.h"
#endif
eliza::~eliza()
{
}
bool eliza::addname(char* n,int d) //add one name and number
{
if(numnames < maxnames)
{
for(int i = strlen(n) - 1; i >= 0; i--)
if(n[i]=='_') n[i]=' ';
// printf("found person '%s' at %d \n",n,d);
return thenames[numnames++].set(n,d) != NULL;
}
return false;
}
int namecompare(const void* a,const void* b)
{
return strcasecmp( ((eliza::nametype*)a)->name,
((eliza::nametype*)b)->name );
}
void eliza::sortnames()
{
qsort((char*)thenames,numnames,sizeof(eliza::nametype),namecompare);
}
int eliza::getname(char* n)
{
nametype finder;
if(finder.set(n,0) == NULL)
return 0;
nametype* nt = (nametype*) bsearch((char*)&finder,(char*)thenames,numnames,
sizeof(nametype),namecompare);
if( nt == NULL)
return 0;
return nt->dbase;
}
void eliza::reducespaces(char *m)
{
int len = strlen(m), space = m[0] < ' ', count = 0;
for(int i=0;i<len;i++)
{
if(!space || m[i]>' ' )
{
m[count]= (m[i]<' ' ? ' ' : m[i]);
count++;
}
space = m[i] < ' ';
}
if(count)
m[count]='\0';
}
int subst(char start[],char sub1[],char sub2[])
{
int ln1 = strlen(sub1),changed=0;
char s[MAXSIZE];
while((start = strstr(start,sub1))!=NULL)
{
changed = 1;
for(int i = 0;i<ln1;i++)
start[i] = ' ';
strcpy(s,start);
sprintf(start,"%s%s",sub2,s);
}
return changed;
}
//Gets a word out of a string.
int fggetword(char*& input,char* outword,char& outother) //0 if done
{
outword[0] = 0;
char *outword0=outword;
int curchar;
curchar=input[0];
while( isalnum(curchar) || curchar=='_' )
{
*outword=curchar;
outword++;
input++;
curchar=*input;
}
if(*input!= '\0') input++;
*outword='\0';
outother=curchar;
if( curchar == 0 && outword0[0]==0 ) return 0;
return 1;
}
//add more then one name to some number
bool eliza::addbunch(char* names,int dbase)
{
char aword[MAXSIZE+3];
char otherch;
char* theinput=names;
while( fggetword(theinput,aword,otherch) )
{
if( addname(aword,dbase) == false )
return false;
}
return true;
}
//This tries more exhaustively to find a name out of the database
//for example if 'Bobby Jo' fails, it will try Bobby then Jo.
int eliza::getanyname(char* n)
{
int dbase=getname(n);
if(dbase == 0)
{
char aword[MAXSIZE+3];
char otherch;
char* theinput = n;
while( fggetword(theinput,aword,otherch) )
{
dbase = getname(aword);
if(dbase != 0)
return dbase;
}
}
return dbase;
}
char *substit(char *in)
{
static const char pairs[][10]=
{ "am","are",
"I","you",
"mine","yours",
"my","your",
"me","you",
"myself","yourself",
//swapped order:
"you","I",
"yours","mine",
"your","my",
"yourself","myself",
"",""
};
for(int i=0;pairs[i*2][0]!='\0';i++)
{
if(strcasecmp(in,pairs[i*2])==0)
{
return (char*)pairs[i*2+1];
}
}
return in;
}
void fixgrammer(char s[])
{
char newsent[MAXSIZE+20];
newsent[0]='\0';
char aword[MAXSIZE+3];
char* theinput;
theinput=s;
char otherch;
char ministr[]=" ";
while( fggetword(theinput,aword,otherch))
{
#ifdef DEBUG
printf(">%s'%s' : '%s' , %d\n",newsent,theinput,aword,otherch);
#endif
ministr[0]=otherch;
strcat(newsent,substit(aword));
strcat(newsent,ministr);
}
strcpy(s,newsent);
}
//enables $variable translation
void eliza::addrest(char* replied, char* talker,
char* rep,char* target,char* rest)
{
trim(rest);
char* i;
char* point = replied;
char* str = rep;
char* toofar = replied+repsize-1;
while ( *str != '\0')
{
if ( *str != '$' )
{
*point++ = *str++;
if(point==toofar)
{
*point='\0'; return;
}
continue;
}
++str;
switch( *str ) /*add $Variable commands here*/
{
case 'n':
i=talker;
break;
case 't':
i=target;
break;
case 'r':
i=rest;
break;
case '$':
i="$";
break;
case 0:
continue;
case 'A': //dont change THIS - Chris Busch //author
i=(char*)eliza_title;
break;
case 'V': //dont change THIS! //Version
i=(char*)eliza_version;
break;
case 'C': //dont change this! //Compile time
i= __DATE__ " " __TIME__;
break;
default:
i="$UNDEFINED";
break;
}
++str;
while ( ( *point = *i ) != '\0' )
{
++point;
++i;
if(point==toofar)
{
*point='\0'; return;
}
}
}
*point++ = '\0';
}
//nothing can be NULL!!!
char* eliza::processdbase(char* talker,char* message,char* target,int dbase)
{
static char replied[repsize];
char *rep=NULL;
if( dbase<0 || dbase>numdbases) return "dbase error in processnumber";
if(strlen(message)>MAXSIZE) return "You talk too much.";
strcpy(replied,"I dont really know much about that, say more.");
reducespaces(message);
trim(message);
int overflow=10; //runtime check so we dont have circular cont jumps
do
{
thekeys[dbase].reset();
while(thekeys[dbase].curr()!=NULL)
{
int i=0,rest=0;
if(match(thekeys[dbase].curr()->key.getlogic(),message,i,rest))
{
rep=thekeys[dbase].curr()->key.getrndreply();
fixgrammer(&message[rest]);
addrest(replied,talker,rep,target,&message[rest]);
reducespaces(replied);
return replied;
}
thekeys[dbase].advance();
}
dbase=thekeys[dbase].contdbase;
overflow--;
}
while(dbase>=0 && overflow>0);
if(overflow <=0)
{
return "potential circular cont (@) jump in databases";
}
return replied;
}
bool eliza::loaddata(char file[],char recurflag)
{
//NOTE!!!! numdbases is the count of dbases!!!
if(numdbases>=maxdbases) return false;
FILE *data;
char str[MAXSIZE];
if((data=fopen(file,"rt"))==NULL)
{
#ifdef DEBUG
puts("File error!");
#endif
return false;
}
int linecount=0;
while(fgets(str,MAXSIZE-1,data)!=NULL)
{
linecount++;
#ifdef DEBUG
puts(str);
#endif
trim(str);
if(strlen(str)>0)
{
if(str[0]>='1' && str[0]<='9')
{
thekeys[numdbases].curr()->key.addreply( str[0]-'0', &(str[1]) );
#ifdef DEBUG
//puts("reply found");
#endif
}
else switch(str[0])
{
case '\0' :
break;
case '(':
thekeys[numdbases].addkey();
thekeys[numdbases].curr()->key.addlogic(str);
#ifdef DEBUG
//puts("logic found");
#endif
break;
case '#':
#ifdef DEBUG
puts("rem");
#endif
break;
case '"':
fprintf(stderr,"%s\n",&str[1]);
break;
case '\'':
fprintf(stdout,"%s\n",&str[1]);
break;
case '>': //add another database
if(numdbases<maxdbases-1) numdbases++;
else
fputs("response database is full. numdbases>maxdbases",stderr);
if(!addbunch(&str[1],numdbases) )
{
puts("name database is full.");
}
break;
case '%': //include another file inline
trim(str+1);
if(!loaddata(str+1,1)) //recurflag set
printf("including inline '%s' failed at %d!\n",str+1,linecount);
break;
case '@': //add continue to another database jump
if(thekeys[numdbases].contdbase==allkeys::contnotset)
{
trim(str+1);
for(char* i=str+1;*i!=0;i++) if(*i=='_') *i=' ';
sortnames();
int contd=getname(str+1);
thekeys[numdbases].contdbase=contd;
// printf("found cont link %s to %d\n",str,contd);
}
else
printf(" @ continue already used for database %d at %d\n"
,numdbases,linecount);
break;
default:
printf("extraneous line: '%s' at %d\n",str,linecount);
}
}
}
fclose(data);
if(!recurflag)
{
numdbases++;
sortnames();
}
return true;
}
char* eliza::trim(char str[])
{
int i,a,ln;
for(ln=strlen(str);(ln>0) && (str[ln-1]<=' ');ln--);
str[ln]=0;
for(i=0;(i<ln) && (str[i]<=' ');i++);
if(i>0)
for(ln-=i,a=0;a<=ln;a++)
str[a]=str[a+i];
return str;
}
int eliza::doop(char op,int a,int b)
{
switch(op)
{
case '\0':
case '&': return a && b;
case '|': return a || b;
case '~': return a && !b;
default:
#ifdef DEBUG
printf("Errored Op! %c\n",op);
#endif
return 0;
}
}
int eliza::lowcase(char ch)
{
if(ch>='A' && ch<='Z') return ch+32;
return ch;
}
int eliza::strpos(char *s,char *sub)
{
int i,a,lns=strlen(s),lnsub=strlen(sub),run,space=1;
if(sub[0]=='=')
{
// = exact equality operator
return strcasecmp(&sub[1],s) ? 0 : lns;
}
if(sub[0]=='^')
{
// match start operator
return strncasecmp(&sub[1],s,lnsub-1) ? 0 : lns;
}
if(lnsub>lns) return 0;
run=lns-lnsub;
for(i=0;i<=run;i++)
{
if(space)
{
for(a=0;a<lnsub && (lowcase(s[i+a])==sub[a]);a++);
#ifdef DEBUG
//printf("hey %d r:%d",a,i+a);
#endif
if(a==lnsub) return (i+a);
}
space=s[i]==' ';
}
return 0;
}
#define encounterop \
trim(ph); \
if(strlen(ph)) \
{ \
a=strpos(m,ph); \
if(a>0) rest=a; \
res=doop(op,res,a); \
} \
len=ph[0]=0;
int eliza::match(char s[],char m[],int& in,int& rest)
{
int l=strlen(s),res=1,op='\0',a;
char ph[MAXSIZE];
int len=0;
ph[0]=0;
#ifdef DEBUG
//puts(s);
//puts(m);
#endif
while(s[in]!='(') in++;
in++;
for(;in<l;in++)
{
switch(s[in])
{
case '(':
#ifdef DEBUG
//printf("in %d into...\n",in);
#endif
a=match(s,m,in,rest);
if(op=='\0') res=a;
else
res=doop(op,res,a);
break;
case '&':
encounterop
op='&';
break;
case '|':
encounterop
op='|';
break;
case '~':
encounterop
op='~';
break;
case ')':
#ifdef DEBUG
//printf("p:'%s'\n",ph);
#endif
trim(ph);
if(strlen(ph))
{
a=strpos(m,ph);
if(a>0) rest=a;
return doop(op,res,a);
}
else return res;
break;
default:
ph[len++]=s[in];
ph[len]='\0';
}
}
return res;
}