ain@j.cc.purdue.edu (Patrick White) (05/15/88)
Submitted by: jdm@gryphon.cts.com (John Mesiavech) Summary: a tiny prolog interpreter. Poster Boy: Patrick White (ain@j.cc.purdue.edu) Archive Name: sources/amiga/volume4/prolog.s.sh.Z tested. NOTES: Converted it from arc to shar. It dosen't do everything CProlog can do, but it works. -- Pat White (co-moderator comp.sources/binaries.amiga) ARPA/UUCP: j.cc.purdue.edu!ain BITNET: PATWHITE@PURCCVM PHONE: (317) 743-8421 U.S. Mail: 320 Brown St. apt. 406, West Lafayette, IN 47906 ======================================== # This is a shell archive. # Remove everything above and including the cut line. # Then run the rest of the file through sh. #----cut here-----cut here-----cut here-----cut here----# #!/bin/sh # shar: Shell Archiver # Run the following text with /bin/sh to create: # prolog.doc # prolog.c # prolog.h # This archive created: Fri May 13 14:49:34 1988 # By: Patrick White (PUCC Land, USA) echo shar: extracting prolog.doc '(2792 characters)' cat << \SHAR_EOF > prolog.doc VT-PROLOG - Very Tiny Prolog VT-PROLOG is a simple prolog interpreter provided with full source code to encourage experimentation with PROLOG. Loading a data base 1. You may enter rules and queries directlty from the keyboard. The syntax of both queries and rules is described below. Be sure to terminate all queries, rules and commands with a period. 2. Data bases may be stored as ASCII text files. To read a text file type the command: @ filename . where filename is a legitimate DOS filename, the default extension is 'PRO'. If the filename contains a ':', '.' or a '\' then it must be enclosed in single quotes. For example, the following are legitmate file commands: @ wine . @ 'df0:wine.pro' . @ 'df1:prolog/test/wine' . 3. VTPROLOG will read and compile the text file. Any queries included in the file will be executed just as if they had been typed from the keyboard. 4. Data base files may contain commands to read to other data base files. Terminating VTPROLOG 1. To exit VTPROLOG, type : EXIT . Don't forget the period. VTPROLOG Grammar The following BNF describes the syntax of VTPROLOG rules and queries: sentence ::- rule | query | command rule ::- head '.' | head ':-' tail '.' query ::- '?-' tail '.' command ::- '@' file_name '.' head ::- goal tail ::- goal | goal ',' tail goal ::- constant | variable | structure constant ::- {quoted string} | {token beginning with 'a' .. 'z'} variable ::- {identifier beginning with 'A' .. 'Z' or '_' } structure ::- functor '(' component_list ')' functor ::- {token beginning with 'a' .. 'z'} component_list ::- term | term ',' component_list term ::- goal | list list ::- '[]' | '[' element_list ']' element_list ::- term | term ',' element_list | term | term file_name ::- {legitimate DOS file name, must be surrounded with single quotes if it contains a '.',':' or '\'} Constant, variable or functor names may be up to 80 characters in length. Constants beginning with capital letters or containing imbedded blanks, commas, paraenthesis or periods must be surrounded by single quote marks. Lists begin with '[' and end with ']'. Components separated by commas and may be constants variables, structures or other lists. Good luck with VTPROLOG. We would be very interested in hearing of your experiments, enhancements or even (gasp) bugs that you may find. Please write to us with your comments or questions. Bill and Bev Thompson C/O AI Expert Magazine 650 5th St. Suite 311 San Francisco, CA 94107 SHAR_EOF if test 2792 -ne "`wc -c prolog.doc`" then echo shar: error transmitting prolog.doc '(should have been 2792 characters)' fi echo shar: extracting prolog.c '(34136 characters)' cat << \SHAR_EOF > prolog.c #include <stdio.h> #include "prolog.h" node *copylist(); boolean unify(); main() { initialize() ; compile(stdin) ; } /* Copyright 1986 - MicroExpert Systems Box 430 R.D. 2 Nassau, NY 12123 */ /* Revisions - 1.1 Nov. 1986 - Edinburgh list syntax added */ /* 11-9-87 converted to lattice c by Dennis J. Darland [73300,270] */ /* VTPROLOG implements the data base searching and pattern matching of PROLOG. It is described in "PROLOG from the Bottom Up" in issues 1 and 2 of AI Expert. Tested on AMIGA lattice c. Requires -cu option for unsigned char's. We would be pleased to hear your comments, good or bad, or any applications and modifications of the program. Contact us at: AI Expert CL Publications Inc. 650 Fifth St. Suite 311 San Francisco, CA 94107 or on the AI Expert BBS. Our id is BillandBev Thompson ,[76703,4324]. You can also contact us on BIX, our id is bbt. Bill and Bev Thompson */ /* ---------------------------------------------------------------------- Utility Routines ---------------------------------------------------------------------- */ int indelim(ch) register unsigned char ch; { return (ch == ' ' || ch == ')' || ch == '(' || ch == ',' || ch == '[' || ch == ']' || ch == tab || ch == quote_char || ch == ':' || ch == '@' || ch == '.' || ch == 0xff || ch == '?' || ch == '|'); } int isconsole(f) register FILE *f; /* return true if f is open on the system console for details of fibs and fibptrs see the Turbo Pascal ver 3.0 reference manual chapter 20. This should work under CP/M-86 or 80, but we haven't tried it. */ { return(f == stdin); } /* isconsole */ stripleadingblanks(s) register char *s; { if (strlen(s) > 0) { if ((s[0] == ' ') || (s[0] == tab)) { delete(s,0,1) ; stripleadingblanks(s) ; } } } /* stripleadingblanks */ striptrailingblanks(s) register char *s; { if (strlen(s) > 0) { if ((s[strlen(s)-1] == ' ') || (s[strlen(s)-1] == tab)) { delete(s,strlen(s)-1,1) ; striptrailingblanks(s) ; } } } /* striptrailingblanks */ int isnumber(s) register char *s; /* checks to see if s contains a legitimate numerical string. It ignores leading and trailing blanks */ { int num; register int code; striptrailingblanks(s) ; stripleadingblanks(s) ; if (strlen(s) > 0) code =stcd_i(s,&num); else code = -1 ; return(code >0) ; } /* isnumber */ /* double cardinal(i) register int i; { double r; r = i ; return(r); }*/ /* cardinal */ node *head(list) register node *list; /* returns a pointer to the first item in the list. If the list is empty, it returns NULL. */ { if (list == NULL) return(NULL); else return(list->node_union.cons_node.head_ptr) ; } /* head */ node *tail(list) register node *list; /* returns a pointer to a list starting at the second item in the list. Note - tail( (a b c) ) points to the list (b c), but tail( ((a b) c d) ) points to the list (c d) . */ { if (list == NULL) return( NULL); else { switch (list->tag) { case consnode : return(list->node_union.cons_node.tail_ptr) ; break; default : return(NULL); break; } } } /* tail */ char *stringval(list) register node *list; /* returns the string pointed to by list. If list points to a number node, it returns a string representing that number */ { if (list == NULL) { return(NULL); } else if ((list->tag ==constant) || (list->tag ==variable) || (list->tag ==func)) { return(list->node_union.string_data); } else { return(NULL); } } /* stringval */ enum node_type tagvalue(list) register node *list; /* returns the value of the tag for a node. */ { return(list->tag) ; } /* tagvalue */ printlist(list) register node *list; /* recursively traverses the list and prints its elements. This is not a pretty printer, so the lists may look a bit messy. */ { register node *p; if (list != NULL) { switch (list->tag) { case constant: case func: case variable : printf("%s ",stringval(list)); break; case consnode : printf("(") ; p = list ; while (p != NULL) { if (tagvalue(p) == consnode) printlist(head(p)); else printlist(p) ; p = tail(p) ; } printf(") ") ; break; } } } /* printlist */ node *allocstr(typ,s) enum node_type typ; register char *s; /* Allocate storage for a string. */ { register node *pt; pt = (node *)malloc(sizeof(node)) ; add_chain(pt); pt->tag = typ ; strcpy(pt->node_union.string_data, s) ; return(pt ); } /* allocstr */ node *cons(newnode,list) register node *newnode,*list; /* Construct a list. This routine allocates storage for a new cons node. newnode points to the new head of the list. The tail pointer of the new node points to list. This routine adds the new cons node to the beginning of the list and returns a pointer to it. The list described in the comments at the beginning of the program could be constructed as cons(allocstr('A'),cons(allocstr('B'),cons(allocstr('C'),NULL))). */ { register node *p; p = (node *) malloc(sizeof(node)) ; add_chain(p); p->tag = consnode ; p->node_union.cons_node.head_ptr = newnode ; p->node_union.cons_node.tail_ptr = list ; return( p) ; } /* cons */ node *appendlist(list1,list2) register node *list1,*list2; /* Append list2 to list1. This routine returns a pointer to the combined list. Appending is done by consing each item on the first list to the second list. This routine is one of the major sources of garbage so if garbage collection becomes a problem, you may want to rewrite it. */ { if (list1 == NULL) return(list2); else return(cons(head(list1),appendlist(tail(list1),list2))) ; } /* appendlist */ counter listlength(list) register node *list; /* returns the length of a list. Note - both (A B C) and ( (A B) C D) have length 3. */ { if (list == NULL) return(0); else return(1 + listlength(list->node_union.cons_node.tail_ptr)) ; } /* listlength */ collectgarbage() { printf("*") ; unmarkmem() ; mark(saved_list) ; freemem() ; } /* end collectgarbage scope */ testmemory() { if (chain_cnt > MAX_ALLOC) collectgarbage() ; } /* testmemory */ wait() /* Just like it says. It waits for the user to press a key before continuing. */ { register char ch; printf("\n") ; printf("\n") ; printf("Press any key to continue.\n ") ; ch = getchar(); printf("\n") ; } /* wait */ /* ------------------------------------------------------------------------ End of utility routines ------------------------------------------------------------------------ */ readfromfile(f) register FILE *f; /* Read a line from file f and store it in the global variable line. It ignores blank lines and when the end of file is reached an eofmark is returned. */ { register unsigned char *cp; register int test; for (cp=line; cp<&line[131]; cp++) { test = fgetc(f); if (test == EOF) { *cp++ = 0xff; *cp = 0; break; } else *cp = test; if (*cp == '\n') { *cp = '\0'; break; } } } /* readfromfile */ /* end readfromfile scope */ gettoken(tline,token) register char *tline; register char *token; /* Extract a token from tline. Comments are ignored. A token is a string surrounded by delimiters or an end of line. Tokens may contain embedded spaces if they are surrounded by quote marks */ { stripleadingblanks(tline) ; if (strlen(tline) > 0) { if (strncmp(tline,"/*",2)== 0) { comment(tline); } else if ((strncmp(tline,":-",2) == 0) || (strncmp(tline,"?-",2) == 0)) { strncpy(token,tline,2) ; token[2] = 0; delete(tline,0,2) ; } else if (tline[0] == quote_char) getquote(tline); else if (indelim(tline[0])) { token[0] = tline[0] ; token[1] = 0; delete(tline,0,1) ; } else getword(tline) ; } else token[0] = '\0' ; } /* gettoken */ getword(tline) register char *tline; { register boolean done; register int cn; register int len; cn = 0 ; len = strlen(tline) ; done = false ; while (! done) { if (cn > len) done = true; else if (indelim(tline[cn])) done = true; else cn++; } strncpy(token,tline,cn) ; token[cn] = 0; delete(tline,0,cn) ; } /* getword */ int pos(p1,p2) register char *p1,*p2; { register int len; char *p3; len = stcpm(p2,p1,&p3); if (len >0) return((int)p3-(int)p2); else return(-1); } delete(p1,pos,n) register char *p1; register int pos,n; { int i; for (i=pos;;i++) { p1[i]=p1[i+n]; if (p1[i] == 0) break; } } comment(tline) register char *tline; { if (pos("*/",tline) >=0) { delete(tline,0,pos("*/",tline)+1) ; gettoken(line,token) ; } else { tline[0] = '\0' ; token[0] = '\0' ; in_comment = true ; } } /* comment */ getquote(tline) register char *tline; { register int i; delete(tline,0,1) ; if (pos(quote_char,tline) >= 0) { token[0] = quote_char; for (i=1;i<=pos("'",tline);i++) token[i]=tline[i]; token[i]=0; delete(tline,0,pos(quote_char,tline)) ; } else { strcpy(token,tline) ; tline[0] = '\0' ; } } /* getquote */ /* end scope gettoken */ scan(f,token) register FILE *f; register char *token; /* Scan repeatedly calls gettoken to retreive tokens. When the end of a line has been reached, readfromfile is called to get a new line. */ { if (strlen(line) > 0) { gettoken(line,token) ; } else { readfromfile(f) ; scan(f,token) ; } } /* scan */ compile(source) register FILE *source; /* The recursive descent compiler. It reads tokens until the token 'EXIT' is found. If the token is '?-', a query is performed, a '@' token is the command to read a new file and source statements are read form that file, otherwise the token is assumed to be part of a sentence and the rest of the sentence is parsed. */ { scan(source,token) ; while (token[0] != 0xff) { error_flag = false ; if (strncmp(token,"?-",2)== 0) { scan(source,token) ; query(source) ; } else if (strcmp(token,"@")== 0) { scan(source,token) ; readnewfile(source) ; } else if (strncmp(token,"EXIT",4)==0) doexit(source); else if (token[0] == 0xff) break; else rule(source) ; scan(source,token) ; } } /* compile */ error(errormsg,source) register char *errormsg; register FILE *source; /* Signal an error. Prints saved_line to show where the error is located. saved_line contains the current line being parsed. it is required, because gettoken chews up line as it reads tokens. */ { error_flag = true ; printf("\n") ; printf(errormsg) ; printf("\n") ; /* printf(saved_line) ; */ /* writeln(" : strlen(saved_line) - strlen(line) - 1,^") ; ;*/ if (isconsole(source)) { token[0] = '.' ; token[1] = 0; line[0] = '\0' ; } else runout(source) ; wait() ; } /* error */ runout(source) register FILE *source; { while ((strcmp(token,".") != 0) && (token[0] != 0xff)) scan(source,token) ; } /* runout */ /* end scope error*/ goal(lptr,source) register node **lptr; register FILE *source; /* Read a goal. The new goal is appended to lptr. Each goal is appended to lptr as a list. Thus, the sentence 'likes(john,X) :- likes(X,wine) .' becomes the list ( (likes john X) (likes X wine) ) */ { char goaltoken[80]; if ((token[0] >='a' && token[0] <= 'z') || token[0] == quote_char) { if (token[0] == quote_char) { *lptr = appendlist(*lptr,cons(cons(allocstr(constant, &token[1]),NULL),NULL)) ; scan(source,token) ; } else { strcpy(goaltoken,token) ; scan(source,token) ; if (token[0] == '(') functor(lptr,goaltoken,source); else *lptr = appendlist(*lptr, cons(cons(allocstr(constant,goaltoken),NULL),NULL)) ; } } else error("A goal must begin with 'a .. z' or be a quoted string.",source) ; } /* goal */ functor(fptr, functoken,source) register node **fptr; register char *functoken; register FILE *source; /* The current goal is a functor. This routine allocates a node to store the functor and processes the components of the functor. On exit, fptr points to the list containing the functor and its components. functoken contains the functor name. */ { node *cptr; cptr = cons(allocstr(func,functoken),NULL) ; scan(source,token) ; components(&cptr,source) ; if (token[0] == ')') { *fptr = appendlist(*fptr,cons(cptr,NULL)) ; scan(source,token) ; } else error("Missing ')'.",source) ; } /* functor */ components(cmptr,source) register node * *cmptr; register FILE *source; /* Process the components of the functor. The components are terms seperated by commas. On exit, cmptr points to the list of components. */ { term(cmptr,source) ; if (token[0] == ',') { scan(source,token) ; components(cmptr,source) ; } } /* components */ term(tptr,source) register node * *tptr ; register FILE *source; /* Process a single term. The new term is appended to tptr. */ { char ttoken[80]; if (token[0] >= 'A' && token[0] <= 'Z') varbl(tptr,source); else if (token[0] == quote_char) quotedstr(tptr,source); else if (isnumber(token)) number(tptr,source); else if (token[0] == '[') list(tptr,source); else if (token[0] >= 'a' && token[0] <= 'z') { strcpy(ttoken, token) ; scan(source,token) ; if (token[0] == '(') functor(tptr,ttoken,source); else *tptr = appendlist(*tptr,cons(allocstr(constant,ttoken),NULL)) ; } else error("Illegal Symbol.",source) ; } /* term */ quotedstr(qptr,source) register node * *qptr; register FILE *source; /* Process a quote */ { *qptr = appendlist(*qptr,cons(allocstr(constant,&token[1]),NULL)) ; scan(source,token) ; } /* quotedstr */ varbl(vptr,source) register node * *vptr ; register FILE *source; /* The current token is a varaible, allocate a node and return a pointer to it. */ { *vptr = appendlist(*vptr,cons(allocstr(variable,token),NULL)) ; scan(source,token) ; } /* varbl */ number(nptr,source) register node * *nptr; register FILE *source; /* Numbers are treated as string constants. This isn't particularly efficent, but it is easy. */ { *nptr = appendlist(*nptr,cons(allocstr(constant,token),NULL)) ; scan(source,token) ; } /* number */ list(lptr,source) register node * *lptr ; register FILE *source; /* A list may either be empty, [], or it may be an group of elements surrounded by brackets. On return, lptr has the list structure appended to it. */ { node *elemlist; scan(source,token) ; if (token[0] == ']') { *lptr = appendlist(*lptr,cons(NULL,NULL)) ; scan(source,token) ; } else { elemlist = NULL ; elementlist(&elemlist,source) ; if (token[0] == ']') { scan(source,token) ; *lptr = appendlist(*lptr,cons(elemlist,NULL)) ; } else error("Missing ']'.",source) ; } } /* list */ elementlist(elist,source) register node * *elist ; register FILE *source; /* The element list is a group of terms separated by commas */ { node *elist2; term(elist,source) ; if (token[0] == ',') { scan(source,token) ; elementlist(elist,source) ; } else if (token[0] == '|') { elist2 = NULL ; scan(source,token) ; term(&elist2,source) ; *elist = appendlist(*elist,head(elist2)) ; } } /* elementlist */ /* end scope list */ /* end scope term */ /* end scope components */ /* end scope functor */ /* end scope goal */ taillist(tptr,source) register node * *tptr ; register FILE *source; /* Process the tail of a rule. Since the a query is syntactically identical to the tail of a rule, this routine is used to compile queries. On exit, tptr points to the list containing the tail. */ { goal(tptr,source) ; if (token[0] == ',') { scan(source,token) ; taillist(tptr,source) ; } } /* taillist */ rule(source) register FILE *source; /* Procees a rule, actually any sentence. If no error occurs the new sentence is appended to the data base. */ { node * rptr; saved_list = cons(data_base,NULL) ; testmemory() ; rptr = NULL ; headlist(&rptr,source) ; if (strcmp(token,":-")==0) { scan(source,token) ; taillist(&rptr,source) ; } if (token[0] != '.') error("'.' expected.",source) ; if (! error_flag) data_base = appendlist(data_base,cons(rptr,NULL)) ; } /* rule */ headlist(hptr,source) register node * *hptr ; register FILE *source; { goal(hptr,source) ; } /* head */ /* end scope rule */ query(source) register FILE *source; /* Process a query. Compile the query, and call solve to search the data base. qptr points to the compiled query and solved is a boolean indicating whether the query was successfully solved. */ { node *qptr; boolean solved; qptr = NULL ; taillist(&qptr,source) ; if (token[0] != '.') error("''.'' expected.",source); else if (! error_flag) { solved = false ; saved_list = cons(data_base,NULL) ; solve(qptr,NULL,0,&solved) ; if (! solved) printf("No\n") ; } } /* query */ solve(list,env,level,solved) register node *list; node *env; register counter level; register boolean *solved; /* This is where all the hard work is done. This routine follows the steps outlined in the article. list is the query to be soved, env is the current environment and level is the recursion level. level can only get to 32767, but you'll run out of stack space long before you get that far. solve saves list and env on the saved list so that they won't be destroyed by garbage collection. The data base is always on the saved list. At the end of solve, list and env are removed from saved_list. */ { node *newenv; register node *p; saved_list = cons(list,cons(env,saved_list)) ; if (list == NULL ) { checkcontinue(solved,&env,level); } else { p = data_base; while (p && !(*solved)) { testmemory() ; if (unify(copylist(head(head(p)),level),head(list),env,&newenv)) { solve(appendlist(copylist(tail(head(p)),level),tail(list)), newenv,level + 1,solved) ; } p = tail(p); } } saved_list = tail(tail(saved_list)) ; } /* solve */ node *lookup(varstr, environ) register char *varstr; register node * environ; /* Search the environment list pointed to by environ for the variable, varstr. If found return a pointer to varstr's binding, otherwise return NULL */ { register boolean found; register node * p; p = environ ; found = false ; while ((p != NULL) && (! found)) { if (strcmp(varstr,stringval(head(head(p))))==0) { found = true ; return(tail(head(p))) ; } else p = tail(p) ; } if (! found) return( NULL) ; } /* lookup */ checkcontinue(solved,env,level) register boolean *solved; register node * *env; register int level; /* Print the bindings and see if the user is satisfied. If nothing is printed from the environment, print 'Yes' to indicate that the query was successfully satisfied. */ { boolean printed, listprinting; register char ch; printed = false ; listprinting = false ; printbindings(*env,&listprinting,&printed,env) ; if (! printed && level == 0) { printf("\n") ; printf("Yes\n ") ; printf("Press 'm' for more or 'q' to quit.\n"); do ch = getchar() ; while (ch!= 'm' && ch != 'q'); *solved = (ch == 'q') ; } else if (printed) { printf("\n") ; printf("Press 'm' for more or 'q' to quit.\n"); do ch = getchar() ; while (ch!= 'm' && ch != 'q'); *solved = (ch == 'q') ; } } /* checkcontinue */ printbindings(list,listprinting,printed,env) register node * list ; register boolean *listprinting; register boolean *printed; register node * *env; /* Print the bindings for level 0 variables only, intermediate variables aren't of interest. The routine recursivley searches for the end of the environments list and prints the binding. This is so that variables bound first are printed first. */ { if (list != NULL) { printbindings(tail(list),listprinting,printed,env) ; if (pos("#",stringval(head(head(list)))) == -1) { *printed = true; printf("\n"); printf("%s == ",stringval(head(head(list)))) ; switch (tagvalue(tail(head(list)))) { case constant : printf("%s ",stringval(tail(head(list)))) ; break; case variable : printvariable(stringval(tail(head(list))),listprinting,env) ; break; case consnode : printalist(tail(head(list)),listprinting,env) ; break; } } } } /* printbindings */ printvariable(varstr,listprinting,env) register char *varstr; register boolean *listprinting; register node * *env; /* The varaible in question was bound to another varaible, so look up that variable's binding and print it. If a match can't be found print '' to tell the user that the variable is anonymous. */ { node *varptr; varptr = lookup(varstr,*env) ; if (varptr != NULL) { switch (tagvalue(varptr)) { case constant : printf("%s ",stringval(varptr)) ; break; case variable : printvariable(stringval(varptr),env) ; break; case consnode : if (*listprinting) printcomponents(varptr,listprinting,env); else printalist(varptr,listprinting,env) ; break; } } else printf(" ") ; } /* printvariable */ printfunc(p ,listprinting,env) register node * p ; register boolean *listprinting; { printf("%s",stringval(head(p))) ; printf("(") ; printcomponents(tail(p),listprinting,env) ; printf(")") ; } /* printfunc */ printcomponents(p,listprinting,env) register node * p; register boolean *listprinting; register node * *env; /* Print the components of a functor. These may be variables or other functors, so call the approriate routines to print them. */ { if (p != NULL) { switch (tagvalue(p)) { case constant : printf("%s ",stringval(p)) ; break; case variable : printvariable(stringval(p),env) ; break; case consnode : if (tagvalue(head(p)) == func) printfunc(p,listprinting,env); else { if (tagvalue(head(p)) == consnode) printalist(head(p),listprinting,env); else printcomponents(head(p),listprinting,env) ; if (tail(p) != NULL) { printf(",") ; printcomponents(tail(p),listprinting,env) ; } } break; } } } /* printcomponents */ printalist(l,listprinting,env) register node * l; register boolean *listprinting; register node * *env; /* The variable was bound to a functor. Print the functor and its components. */ { if (l != NULL) { if (tagvalue(head(l)) == func) printfunc(l,listprinting,env); else { *listprinting = true ; printf("[") ; printcomponents(l,listprinting,env) ; printf("]") ; } } } /* printalist */ /* end scope printbindings */ /* end scope checkcontinue */ node *copylist(list , copylevel) register node * list; counter copylevel; /* Copy a list and append the copylevel (recursion level) to all variables. */ { node *templist; char levelstr[8]; sprintf(levelstr,"#%d",copylevel); templist = NULL ; listcopy(list,&templist,©level,levelstr) ; return( templist) ; } /* copylist */ listcopy(fromlist,tolist,copylevel,levelstr) register node * fromlist; register node * *tolist; register counter *copylevel; register char *levelstr; { if (fromlist != NULL) { char temp[132]; switch (fromlist->tag) { case variable : sprintf(temp,"%s%s",fromlist->node_union.string_data,levelstr); *tolist = allocstr(variable,temp) ; break; case func: case constant : *tolist = fromlist ; break; case consnode : listcopy(tail(fromlist),tolist,copylevel,levelstr) ; *tolist = cons(copylist(head(fromlist),*copylevel),*tolist) ; break; } } } /* listcopy */ /* end scope copylist */ boolean unify(list1,list2,environ,newenviron) node *list1,*list2,*environ ; register node **newenviron; /* Unify two lists and return any new bindings at the front of the environment list. Returns true if the lists could be unified. This routine implements the unification table described in the article. Unification is straight forward, but the details of matching the lists get a little messy in this routine. There are better ways to do all of this, we just haven't gotten around to trying them. If you implement any other unification methods, we would be glad to hear about it. Unify checks to see if both lists are NULL, this is a successful unification. Otherwise check what kind on node the head of list1 is and call the appropriate routine to perform the unification. Variables are unified by looking up the binding of the variable. If none is found, make a binding for the variable, otherwise try to unify the binding with list2. */ { boolean unifyvar; register boolean uv; node *varptr; if ((list1 == NULL) && (list2 == NULL)) { unifyvar = true ; /* *newenviron = environ ; */ } else if (list1 == NULL) { uv = unify(list2,list1,environ,newenviron); return(uv); } else { switch (tagvalue(list1)) { case constant : unifyconstant(&list1,&list2,&varptr,&environ,newenviron,&unifyvar); break; case variable : unifyvariable(&list1,&list2,&varptr,&environ,newenviron,&unifyvar); break; case func : unifyfunc(&list1,&list2,&varptr,&environ,newenviron,&unifyvar); break; case consnode : unifylists(&list1,&list2,&varptr,&environ,newenviron,&unifyvar); break; default : fail(&environ,newenviron,&unifyvar); break; } } return(unifyvar); } /* unify */ makebinding(l1,l2,environ,newenviron,unifyvar) register node * l1,*l2,**environ,**newenviron; register boolean *unifyvar; /* Bind a variable to the environment. Anonymous variables are not bound. l1 points to the variable and l2 points to its binding. */ { if (strcmp(stringval(l1),"") != 0) { *newenviron = cons(cons(l1,l2),*environ); } else { *newenviron = *environ ; } *unifyvar = true ; } /* makebinding */ fail(environ,newenviron,unifyvar) register node * *environ,**newenviron; boolean *unifyvar; /* Unification failed. */ { *unifyvar = false ; *newenviron = *environ ; } /* fail */ unifyconstant(list1,list2,varptr,environ,newenviron,unifyvar) register node **list1,**list2,**varptr,**environ,**newenviron; boolean *unifyvar; /* List1 contains a constant. Try to unify it with list2. The 4 cases are: list2 contains constant - unify if constants match variable - look up binding, if no current binding bind the constant to the variable, otherwise unify list1 with the binding. consnode, func - these can't be unified with a constant. A consnode indicates an expression. */ { if ((*list2) == NULL) nilconstant(list1); else { switch (tagvalue(*list2)) { case constant : if (strcmp(stringval(*list1),stringval(*list2)) == 0) { *unifyvar = true ; *newenviron = *environ ; } else fail(environ,newenviron,unifyvar) ; break; case variable : *varptr = lookup(stringval(*list2),*environ) ; if ((*varptr) == NULL) makebinding((*list2),(*list1),environ,newenviron,unifyvar); else *unifyvar = unify((*list1),(*varptr),*environ,newenviron) ; break; case consnode: case func: fail(environ,newenviron,unifyvar) ; break; default :fail(environ,newenviron,unifyvar) ; break; } } } /* unifyconstant */ nilconstant(list1,environ,newenviron,unifyvar) register node **list1,**environ,**newenviron; boolean *unifyvar; { if (strcmp(stringval(*list1),"[]") ==0) { *unifyvar = true ; *newenviron = *environ ; } else fail(environ,newenviron,unifyvar) ; } /* nilconstant */ /* end scope unifyconstant */ unifyvariable(list1,list2,varptr,environ,newenviron,unifyvar) register node * *list1,**list2,**varptr,**environ,**newenviron; boolean *unifyvar; /* The first list contained a variable, now try to unify that variable with list2. If list2 is NULL, unify the varaible with '[]'. This is for printing purposes only. */ { *varptr = lookup(stringval(*list1),*environ) ; if ((*varptr) != NULL) *unifyvar = unify(*varptr,*list2,*environ,newenviron); else if (list2 == NULL) makebinding((*list1),allocstr(constant,"[]"), environ,newenviron,unifyvar); else if ((tagvalue(*list2) == constant) || (tagvalue(*list2) == variable) || (tagvalue(*list2) == func) || (tagvalue(*list2) == consnode)) makebinding(*list1,*list2,environ,newenviron,unifyvar); else fail(environ,newenviron,unifyvar) ; } /* unifyvariable */ unifyfunc(list1,list2,varptr,environ,newenviron,unifyvar) register node * *list1,**list2,**varptr,**environ,**newenviron; boolean *unifyvar; /* List1 contains a functor. Try to unify it with list2. The 4 cases are: list2 contains constant - can't be unified. variable - look up binding, if no current binding bind the functor to the variable, otherwise unify list1 with the binding. consnode - fail func - if the functors match, true to unify the component lists (tail of the list) term by term. */ { switch (tagvalue(*list2)) { case constant : fail(environ,newenviron,unifyvar) ; break; case variable : *varptr = lookup(stringval(*list2),*environ) ; if ((*varptr) == NULL) makebinding(*list2,*list1,environ,newenviron,unifyvar); else *unifyvar = unify(*list1,*varptr,*environ,newenviron) ; break; case func : if (strcmp(stringval(*list1),stringval(*list2)) ==0) { *unifyvar = true ; *newenviron = *environ ; } else fail(environ,newenviron,unifyvar) ; break; case consnode : fail(environ,newenviron,unifyvar) ; break; default : fail(environ,newenviron,unifyvar) ; break; } } /* unifyfunc */ unifylists(list1,list2,varptr,environ,newenviron,unifyvar) register node * *list1,**list2,**varptr,**environ,**newenviron; boolean *unifyvar; /* List1 contains an expression. Try to unify it with list2. The 4 cases are: list2 contains constant - can't be unified. variable - look up binding, if no current binding bind the functor to the variable, otherwise unify list1 with the binding. consnode - If the heads can be unified, unify the tails. func - fail */ { switch (tagvalue(*list2)) { case constant : fail(environ,newenviron,unifyvar) ; break; case variable : *varptr = lookup(stringval(*list2),*environ) ; if ((*varptr) == NULL) makebinding(*list2,*list1,environ,newenviron,unifyvar); else *unifyvar = unify(*list1,*varptr,*environ,newenviron) ; break; case func : fail(environ,newenviron,unifyvar) ; break; case consnode : if (unify(head(*list1),head(*list2),*environ,newenviron)) *unifyvar = unify(tail(*list1),tail(*list2),*environ,newenviron); break; default: fail(environ,newenviron,unifyvar) ; break; } } /* unifylists */ /* end scope unify */ /* end scope solve */ /* end scope query */ readnewfile(source) register FILE *source; /* Read source statements from a new file. When all done, close file and continue reading from the old file. Files may be nested, but you will run into trouble if you nest them deaper than 15 levels. This is Turbo's default for open files. */ { register FILE *newfile; char oldline[132],oldsave[132]; char fname[80]; if (token[0] == quote_char) delete(token,0,1) ; if (pos(".",token) == -1) { strcpy(fname,token); strcat(fname,".pro"); } else strcpy(fname , token) ; if ((newfile = fopen(fname,"r"))!= NULL) { strncpy(oldline, line, 132) ; /* strncpy(oldsave, saved_line, 132) ; */ line[0] = '\0' ; compile(newfile) ; fclose(newfile) ; strncpy(line, oldline, 132) ; /* strncpy(saved_line, oldsave, 132) ; */ scan(source,token) ; if (token[0] != '.') error("'.' expected.",source) ; } else error("Unable to open ",source) ; } /* readnewfile */ doexit(source) register FILE *source; /* Exit the program. This really should be a built-in function and handled in solve, but this does the trick. */ { scan(source,token) ; if (token[0] != '.') error("'.' expected.",source); else exit(0); } /* doexit */ /* end scope compile */ initialize() /* Write a heading line and initialize the global variables */ { printf("\n") ; printf( "Very Tiny Prolog - Version 1.1 [c] 1986 MicroExpert Systems\n") ; printf( "Modified from Pascal to C by Dennis Darland\n"); printf ("\n"); in_comment = false ; line[0] = '\0' ; data_base = NULL ; saved_list = NULL; } /* initialize */ mark(list) register node *list; /* Mark the blocks on list as being in use. Since a node may be on several lists at one time, if it is already marked we don't continue processing the tail of the list. */ { if (list != NULL) { if (!list->in_use) { list->in_use = true ; if (list->tag ==consnode) { mark(head(list)) ; mark(tail(list)) ; } } } } unmarkmem() /* Go through memory from initialheap^ to HeapPtr^ and mark each node as not in use. The tricky part here is updating the pointer p to point to the next cell. */ { register node *p; p = chain_head; while (p) { p->in_use = false; p = p->chain_node_ptr.next_in_chain; } } add_chain(p) register node *p; { p->chain_node_ptr.next_in_chain = chain_head; chain_head = p; chain_cnt++; } freemem() /* Go through memory from initialheap^ to HeapPtr^ and mark each node as not in use. The tricky part here is updating the pointer p to point to the next cell. */ { register node *p; register node *q; p = chain_head; q = NULL; while (p) { if( p->in_use == false); { if (q) { q->chain_node_ptr.next_in_chain = p->chain_node_ptr.next_in_chain; free(p); chain_cnt--; } else { chain_head = p->chain_node_ptr.next_in_chain; free(p); chain_cnt--; } } q = p; p = p->chain_node_ptr.next_in_chain; } } SHAR_EOF if test 34136 -ne "`wc -c prolog.c`" then echo shar: error transmitting prolog.c '(should have been 34136 characters)' fi echo shar: extracting prolog.h '(3813 characters)' cat << \SHAR_EOF > prolog.h /* Copyright 1986 - MicroExpert Systems Box 430 R.D. 2 Nassau, NY 12123 */ /* Revisions - 1.1 Nov. 1986 - Edinburgh list syntax added */ /* converted to lattice c by Dennis J. Darland [73300,270] 11/9/87 */ /* VTPROLOG implements the data base searching and pattern matching of PROLOG. It is described in "PROLOG from the Bottom Up" in issues 1 and 2 of AI Expert. We would be pleased to hear your comments, good or bad, or any applications and modifications of the program. Contact us at: AI Expert CL Publications Inc. 650 Fifth St. Suite 311 San Francisco, CA 94107 or on the AI Expert BBS. Our id is BillandBev Thompson ,[76703,4324]. You can also contact us on BIX, our id is bbt. Bill and Bev Thompson */ #define debug 0 #define back_space 8 #define tab '\t' #define eof_mark 26 #define esc 27 #define quote_char 39 #define left_arrow 75 #define end_key = 79 #define del_line 24 #define bell 7 #define true 1 #define false 0 #define MAX_ALLOC 1000 typedef int counter; typedef unsigned char boolean; enum node_type {consnode,func,variable,constant,freenode}; typedef struct node_struct { boolean in_use; enum node_type tag; struct chain_struct { struct node_struct *next_in_chain; } chain_node_ptr; union { struct cons_struct { struct node_struct *tail_ptr; struct node_struct *head_ptr; } cons_node; char string_data[80]; } node_union; } node; /* node is the basic allocation unit for lists. The fields are used as follows: in_use - in_use = false tells the garbage collector that this node is available for re-use. tag - which kind of node this is. cons_node - cons_nodes consist of two pointers. one to the head (first item) the other to the rest of the list. They are the "glue" which holds the list together. The list (A B C) would be stored as ------- -------- -------- | .| . |-----> | .| . |------> | .| . |---> NIL --|----- --|------ --|----- | | | V V V A B C The boxes are the cons nodes, the first part of the box holds the head pointer, then second contains the tail. constant - holds string values, we don't actually use the entire 80 characters in most cases. variable - also conatins a string value, these nodes will be treated as PROLOG variables rather than constants. free_node - the garbage collector frees all unused nodes. */ char line[132],saved_line[132]; unsigned char token[80]; FILE *source_file; boolean error_flag,in_comment; node *data_base,*saved_list; int chain_cnt; node *chain_head; /* The important globals are: source_file - text file containing PROLOG statements. line - line buffer for reading in the text file saved_list - list of all items that absolutely must be saved if garbage collection occurs. Usually has at least the data_base and the currents query attached to it. data_base - a pointer to the start of the data base. It points to a node pointing to the first sentence in the data base. Nodes pointing to sentences are linked together to form the data base. delim_set - set of characters which delimit tokens. chain_cnt - total number of nodes malloc'ed. chain_head - head to chain of all malloc'ed nodes. */ SHAR_EOF if test 3813 -ne "`wc -c prolog.h`" then echo shar: error transmitting prolog.h '(should have been 3813 characters)' fi # End of shell archive exit 0