/* HEPARSE.C Input parsing functions: Available CallLibraryParse FindWord KillWord Match routines AnyObjWord AddAllObjects AddObj AddPossibleObject AdvanceGrammar DomainObj InList ObjWord ObjWordType SubtractObj SubtractPossibleObject TryObj ValidObj Parse ParseError RemoveWord ResetFindObject SeparateWords for the Hugo Engine Copyright (c) 1995-2006 by Kent Tessman */ #include "heheader.h" /* Function prototypes: */ void AddObj(int obj); void AddPossibleObject(int obj, char type, unsigned int w); void AdvanceGrammar(void); int DomainObj(int obj); int InList(int obj); int MatchObject(int *wordnum); int MatchWord(int *wordnum); int ObjWordType(int obj, unsigned int w, int type); void ResetFindObject(void); void SubtractObj(int obj); void SubtractPossibleObject(int obj); void TryObj(int obj); int GetVal(void); /* from heexpr.c */ #define MAXOBJLIST 32 #define STARTS_AS_NUMBER(a) (((a[0]>='0' && a[0]<='9') || a[0]=='-')?1:0) char buffer[MAXBUFFER+MAXWORDS]; /* input buffer */ char errbuf[MAXBUFFER+1]; /* last invalid input */ char line[1025]; /* line buffer */ int words = 0; /* parsed word count */ char *word[MAXWORDS+1]; /* breakdown into words */ unsigned int wd[MAXWORDS+1]; /* " " dict. entries */ unsigned int parsed_number; /* needed for numbers in input */ signed char remaining = 0; /* multiple commands in input */ char parseerr[MAXBUFFER+1]; /* for passing to RunPrint, etc. */ char parsestr[MAXBUFFER+1]; /* for passing quoted string */ char xverb; /* flag; 0 = regular verb */ char starts_with_verb; /* input line; 0 = no verb word */ unsigned int grammaraddr; /* address in grammar */ char *obj_parselist = NULL; /* objects with noun/adjective */ int domain, odomain; /* of object(s) */ int objlist[MAXOBJLIST]; /* for objects of verb */ char objcount; /* of objlist */ char parse_allflag = false; /* for "all" in MatchObject() */ struct pobject_structure pobjlist[MAXPOBJECTS]; /* for possible objects */ int pobjcount; /* of pobjlist */ int pobj; /* last remaining suspect */ int obj_match_state; /* see MatchCommand() for details */ char objword_cache[MAXWORDS]; /* for MatchWord() xobject, etc. */ char object_is_number; /* number used in player command */ unsigned int objgrammar; /* for 2nd pass */ int objstart; /* " " " */ int objfinish; /* " " " */ char addflag; /* true if adding to objlist[] */ int speaking; /* if command is addressed to obj. */ char oops[MAXBUFFER+1]; /* illegal word */ int oopscount = 0; /* # of corrections in a row */ char parse_called_twice; char reparse_everything; char punc_string[64]; /* punctuation string */ char full_buffer = false; static char recursive_call = false; /* to MatchObject() */ /* Necessary for proper disambiguation when addressing a character; i.e., when 'held' doesn't refer to held by the player, etc. */ int parse_location; /* usually var[location] */ /* ADDALLOBJECTS */ void AddAllObjects(int loc) { int i; if (loc==var[player] && domain!=loc) return; /* Try to add everything in the specified domain to objlist[] */ for (i=Child(loc); i!=0; i=Sibling(i)) { if (i==var[xobject]) continue; TryObj(i); if (domain==0) { if (Child(i)) AddAllObjects(i); } } } /* ADDOBJ Adds the object to objlist[], making all related adjustments. */ void AddObj(int obj) { int i; for (i=0; i MAXOBJLIST) objcount = MAXOBJLIST; } /* ADDPOSSIBLEOBJECT Adds as a contender to the possible object list, noting that it was referred to as either a noun or an adjective. */ void AddPossibleObject(int obj, char type, unsigned int w) { int i; if (pobjcount==MAXPOBJECTS) return; for (i=0; i 0) passlocal[1] = domain; else if (speaking && non_grammar==0) passlocal[1] = GrandParent(speaking); /* domain of -1 is an explicit 'parent' */ /* else if (domain==-1) passlocal[1] = parse_location; */ else passlocal[1] = parse_location; } ret = 0; PassLocals(2); temp_stack_depth = stack_depth; SetStackFrame(stack_depth, RUNROUTINE_BLOCK, 0, 0); #if defined (DEBUGGER) DebugRunRoutine((long)findobjectaddr*address_scale); #else RunRoutine((long)findobjectaddr*address_scale); #endif retflag = 0; stack_depth = temp_stack_depth; return ret; } else return 1; } /* CALLLIBRARYPARSE */ void CallLibraryParse(void) { if (parseaddr) { #ifdef DEBUG_PARSER Printout("CallLibraryParse()"); #endif parse_called_twice = false; SetStackFrame(RESET_STACK_DEPTH, RUNROUTINE_BLOCK, 0, 0); ret = 0; PassLocals(0); #if defined (DEBUGGER) DebugRunRoutine((long)parseaddr*address_scale); #else RunRoutine((long)parseaddr*address_scale); #endif retflag = 0; /* Returning non-zero return calls the engine's Parse routine again. */ if (ret) { parse_called_twice = true; Parse(); } #ifdef DEBUG_PARSER if (ret) Printout("CallLibraryParse() returned true"); else Printout("CallLibraryParse() returned false"); #endif } } /* DOMAINOBJ Takes into account the preset domain for checking an object's presence; is 0, -1, or an object number.. */ int DomainObj(int obj) { int yes = false; if (obj != var[actor]) { switch (domain) { case 0: case -1: { if (Parent(obj)==parse_location) yes = true; else if ((parse_allflag) && GrandParent(obj)==parse_location) yes = true; else { if (Parent(obj)==parse_location && !InList(Parent(obj))) yes = true; } if (Peek(grammaraddr)==MULTINOTHELD_T) { if (Parent(obj)==var[actor]) yes = false; } break; } default: { if (Parent(obj)==domain) yes = true; } } } return yes; } /* FINDWORD Returns the dictionary address of . */ unsigned int FindWord(char *a) { unsigned int ptr = 0; int i, p, alen; if (a[0]=='\0') return 0; alen = strlen(a); defseg = dicttable; for (i=1; i<=dictcount; i++) { if (alen==(p = Peek(ptr+2)) && (unsigned char)(MEM(dicttable*16L+ptr+3)-CHAR_TRANSLATION)==(unsigned char)a[0]) { if (!strcmp(GetString(ptr + 2), a)) { defseg = gameseg; return ptr; } } ptr += (p + 1); } /* As a last resort, see if the first 6 characters of the word (if it has at least six characters) match a dictionary word: */ if (alen >= 6) { unsigned int possible = 0; int posscount = 0; ptr = 0; for (i=1; i<=dictcount; i++) { if (alen<=(p = Peek(ptr+2)) && (unsigned char)MEM(dicttable*16L+ptr+3)-CHAR_TRANSLATION==a[0]) { if (!strncmp(GetString(ptr + 2), a, alen)) { /* As long as the dictionary word doesn't contain a space */ if (!strrchr(GetString(ptr+2), ' ')) { possible = ptr; posscount++; } } } ptr += (p + 1); } if (posscount==1) return possible; } defseg = gameseg; return UNKNOWN_WORD; /* not found */ } /* INLIST Checks to see if is in objlist[]. */ int InList(int obj) { int i; for (i=0; iwords) return; for (i=a; i "from" ) The variable is the indicator of what stage object-matching is at: obj_match_state = 0 - haven't matched anything yet obj_match_state = 1 - xobject has been matched obj_match_state = 2 - matching object(s), loading objlist[] obj_match_state = 5 - matching first word/name, i.e., "Bob, " */ int MatchCommand() { int i, j, flag, a, mw = 0, gotspeaker = 0; int wordnum; int numverbs = 0, nextverb = 0; unsigned int ptr, verbptr, nextgrammar; unsigned int obj, propaddr; #ifdef DEBUG_PARSER Printout("Entering MatchCommand()"); #endif odomain = 0; /* Reset these for command-matching */ if (!speaking) { var[actor] = var[player]; parse_location = var[location]; } else { var[actor] = speaking; parse_location = GrandParent(speaking); } if (!strcmp(word[1], "~oops")) { strcpy(parseerr, ""); /* "oops" on its own */ if (words==1 || !strcmp(oops, "")) { ParseError(16, 0); /* "You'll have to make a mistake..." */ return 0; } /* trying to correct more than one word */ if (words > 2 || oopscount) { ParseError(17, 0); /* "...one word at a time..." */ return 0; } /* trying to correct a correction */ if (!strcmp(Left(errbuf, 5), "~oops")) { ParseError(13, 0); return 0; } /* Rebuild the corrected buffer */ oopscount = 1; strcpy(line, word[2]); for (i=1; i<=(int)strlen(errbuf); i++) { if (!strcmp(Mid(errbuf, i, strlen(oops)), oops)) break; } strcpy(buffer, errbuf); buffer[i-1] = '\0'; strcat(buffer, line); strcat(buffer, Right(errbuf, strlen(errbuf) - i - strlen(oops) + 1)); SeparateWords(); if (!Parse()) return 0; CallLibraryParse(); } if (word[1][0]=='.') KillWord(1); /* * STEP 1: Match verb * */ ptr = 64; MatchVerb: #ifdef DEBUG_PARSER Printout("MatchCommand(): Step 1"); #endif if (words==0) return 0; defseg = gameseg; grammaraddr = 0; domain = 0; obj_match_state = 0; xverb = 0; starts_with_verb = 0; objcount = 0; parse_allflag = false; objstart = 0; object_is_number = false; for (i=1; i<=MAXWORDS; i++) objword_cache[i] = 0; var[object] = 0; var[xobject] = 0; var[self] = 0; var[verbroutine] = 0; while ((a = Peek(ptr)) != 255) { defseg = gameseg; /* verb or xverb header */ if (a==VERB_T || a==XVERB_T) { /* Skim through 1 or more verb words */ numverbs = Peek(ptr + 1); verbptr = ptr + 2; for (i=1; i<=numverbs; i++) { /* 0xffff signals something other than a dictionary word--see BuildVerb() in hcbuild.c. This will be the case, like, 1% of the time. */ /* If it is a dictionary word... */ if (PeekWord(verbptr)!=0xffff) { /* If one of the verb words matches the first word in the input line */ if (wd[1]==PeekWord(verbptr)) { grammaraddr = ptr; goto GotVerb; } verbptr += 2; } /* ...otherwise assume it's an object (value) */ else { codeptr = verbptr + 1; /* skip 0xffff */ /* GetVal(), not GetValue(), since it's always a simple value */ obj = GetVal(); /* codeptr can't be >65535 on a 16-bit compiler */ verbptr = (unsigned int)codeptr; propaddr = PropAddr(obj, noun, 0); if (propaddr) { defseg = proptable; a = Peek(propaddr+1); /* obj.#prop */ defseg = gameseg; for (j=1; j<=a; j++) { if (wd[1]==(unsigned)GetProp(obj, noun, j, 0)) { grammaraddr = ptr; goto GotVerb; } } } } } /* Otherwise skip over this verb header */ ptr += 2 + numverbs * 2; } /* anything else */ else ptr += Peek(ptr + 1) + 1; } /* * STEP 2: Match object/character (if no verb match) * */ #ifdef DEBUG_PARSER Printout("MatchCommand(): Step 2"); #endif /* If we hit the end of the grammar without finding a verb match: */ if (Peek(ptr)==255) { /* If we already tried this once */ if (gotspeaker) { if (!starts_with_verb) /* "Better start with a verb..." */ ParseError(2, 0); else /* "That doesn't make any sense..." */ ParseError(6, 0); return 0; } /* See if the command begins with an object (character) name: */ flag = 0; if (AnyObjWord(1)==1) flag = 1; /* No match, ergo an invalid command */ if (flag==0 && nextverb==true) { strcpy(parseerr, ""); ParseError(6, 0); /* "...doesn't make any sense..." */ return 0; } /* No provision made for addressing objects (characters) */ if (flag==0 || speaktoaddr==0) { strcpy(parseerr, ""); ParseError(2, 0); /* "Better start with a verb..." */ return 0; } /* Count how many object words there are */ for (i=2; i<=words; i++) { if (AnyObjWord(i)!=1) break; } /* Try to match the first word to a valid object */ objfinish = i - 1; obj_match_state = 5; i = 1; recursive_call = 0; if (MatchObject(&i) != true) return 0; /* unsuccessful */ speaking = pobj; /* successful */ gotspeaker = true; /* So erase the object name from the start of the line */ for (i=1; i<=objfinish; i++) KillWord(1); if (word[1][0]=='~') KillWord(1); /* If it's a name and that's all...*/ if (words==0) return true; /* ...or else proceed as usual */ ptr = 64; goto MatchVerb; } else if (!gotspeaker) speaking = 0; GotVerb: if (!speaking) { var[actor] = var[player]; parse_location = var[location]; } else { var[actor] = speaking; parse_location = GrandParent(speaking); } obj_match_state = 0; starts_with_verb = 1; strcpy(parseerr, word[1]); if (Peek(grammaraddr)==XVERB_T) xverb = true; grammaraddr += 2 + numverbs * 2; /* * STEP 3: Match proper grammar structure (syntax) * */ #ifdef DEBUG_PARSER Printout("MatchCommand(): Step 3"); #endif /* * (STEP 4: We'll be matching xobject, if any, before object) * */ #ifdef DEBUG_PARSER Printout("MatchCommand(): Step 4"); #endif /* Loop until end of grammar table, or next verb: */ while (Peek(grammaraddr)!=255 && Peek(grammaraddr)!=VERB_T && Peek(grammaraddr)!=XVERB_T) { wordnum = 1; nextgrammar = grammaraddr + Peek(grammaraddr + 1) + 1; /* Loop until end of table or next verb: */ while (Peek(grammaraddr) != 255 && Peek(grammaraddr) != VERB_T && Peek(grammaraddr) != XVERB_T) { mw = MatchWord(&wordnum); if (mw==true) { /* end of both input and grammar */ if (wd[wordnum]==0 && Peek(grammaraddr)==ROUTINE_T) { full_buffer = (char)wordnum; break; } /* end of grammar, not input */ if (Peek(grammaraddr)==ROUTINE_T) { mw = false; goto NextStructure; } } else { /* If error already signalled */ if (mw==2) return 0; /* No match, so try next structure */ else { NextStructure: grammaraddr = nextgrammar; var[object] = 0; var[xobject] = 0; var[verbroutine] = 0; domain = 0; odomain = 0; obj_match_state = 0; xverb = 0; objcount = 0; objstart = 0; break; } } } /* Matched the complete syntax of a verb */ if (mw==true) { var[verbroutine] = PeekWord(grammaraddr + 1); break; } } if (mw != true) { if (mw==false) /* mw = 2 if error already printed */ { /* If there's more grammar to check... */ if (Peek(grammaraddr) != 255) { ptr = grammaraddr; nextverb = true; goto MatchVerb; } /* ...or if we reached the end without a sensible syntax matched: */ strcpy(parseerr, ""); /* "...doesn't make any sense..." */ ParseError(6, 0); } return 0; } /* * STEP 5: Match remaining object(s), if any * */ #ifdef DEBUG_PARSER Printout("MatchCommand(): Step 5"); #endif /* If there are objects waiting to be loaded into objlist[] */ if (objstart) { ResetFindObject(); obj_match_state = 2; recursive_call = false; if (odomain) domain = odomain; grammaraddr = objgrammar; i = objstart; /* If there was a problem matching them */ if (MatchObject(&i)==false) return 0; } /* Got a successfully matched verb with all the requisite parameters (if any). */ if (words < wordnum) remaining = 0; else remaining = (char)(words - wordnum); #ifdef DEBUG_PARSER Printout("MatchCommand(): Leaving"); #endif return true; } /* MATCHOBJECT The argument is the word number we're starting matching on. NOTE: recusive_call is set to 0 if this is the first call. MatchObject() sets it to 1 when calling itself when asking for clarification as to which object is meant. Return -1 on a recursive call to allow parsing to continue. */ #define MAX_MOBJ 16 /* maximum number of matchable object words */ int MatchObject(int *wordnum) { char found_noun = false; int i, j, k, m, flag; int mobjs; unsigned int mobj[MAX_MOBJ]; int allmatch, roomloc; int nextobj = 0, bestobj = 0; char bestavail = 0; /* If this is a recursive call, we're not adding new objects */ if (!recursive_call) addflag = true; stack_depth = 0; pobj = 0; /* possible object */ objcount = 0; /* of objlist[] */ pobjcount = 0; /* of pobjlist[] */ mobjs = 0; /* # of previous words in phrase */ bestavail = 0; /* adjective or noun */ #ifdef DEBUG_PARSER Printout("MatchObject(): Entering"); #endif strcpy(parseerr, ""); do /* starting at word #a */ { /* Check first to make sure it's not a housekeeping word such as "~and" or "~all". */ if (word[*wordnum][0]!='~' && word[*wordnum][0]!='\0') { if (parseerr[0]!='\0') strcat(parseerr, " "); strcat(parseerr, word[*wordnum]); flag = 0; for (i=0; i=(char)m) { if (!bestobj) bestobj = i; bestavail = (char)m; } } /* doesn't match previous words */ else SubtractPossibleObject(i); } /* definitely not this object */ else SubtractPossibleObject(i); } /* If checking the start of an input line, i.e. for a command addressed to an object (character): */ if (obj_match_state==5 && !flag) goto Clarify; } else if (!strcmp(word[*wordnum], "~any")) goto NextLoop; /* "~and", "~all",... */ else goto Clarify; /* Didn't get any suspects */ if (pobjcount==0) { /* If "~and", "~all",... */ if (word[*wordnum][0]=='~') { /* If checking the xobject */ if (obj_match_state==1) { strcpy(parseerr, word[1]); /* "...can't use multiple objects..." (as indirect objects) */ ParseError(7, 0); return 0; } goto Clarify; } /* Got an unmatchable sequence of words */ else { if (obj_match_state==5) { if (!starts_with_verb) /* "Better start with a verb..." */ ParseError(2, 0); else /* "That doesn't make any sense..." */ ParseError(6, 0); } else /* "(no such thing)..." */ ParseError(5, 0); return 0; } } if (word[*wordnum][0]!='~') { /* Go back for next word in this object phrase */ mobjs++; if (mobjs==MAX_MOBJ) { /* "(no such thing)..." */ ParseError(5, 0); return 0; } mobj[mobjs] = wd[*wordnum]; } else { /* Since hitting "~and" or "~all", we've obviously finished an object phrase */ (*wordnum)++; goto Clarify; } NextLoop: (*wordnum)++; /* next word */ if ((*wordnum > words || word[*wordnum][0]=='\0') || *wordnum > objfinish) goto Clarify; } while (true); /* endless loop */ Clarify: #ifdef DEBUG_PARSER Printout("MatchObject(): Clarify"); #endif /* If "~and", "~all",... */ if (word[*wordnum][0]=='~') { /* If checking the xobject or addressing a command */ if (obj_match_state==1 || speaking) { strcpy(parseerr, word[1]); /* "...can't use multiple objects..." (as indirect objects) */ ParseError(7, 0); return 0; } } if (!strcmp(word[*wordnum], "~all")) /* if "~all" is specified */ { parse_allflag = true; /* If one or more words were already matched, however... */ if (mobjs > 0) { ParseError(6, 0); /* "...doesn't make any sense..." */ return 0; } if (!domain) /* no particular domain object specified */ roomloc = parse_location; else roomloc = domain; AddAllObjects(roomloc); (*wordnum)++; /* Done processing the object phrase yet? */ /* only >GET ALL EXCEPT... if we're not done the object phrase */ if (*wordnum<=objfinish && strcmp(word[*wordnum], "~except")) { ParseError(6, 0); /* "Doesn't make any sense..." */ return 0; } if ((*wordnum > words || word[*wordnum][0]=='\0') || (obj_match_state != 1 && *wordnum >= objfinish)) { if (!objcount && !speaking) { strcpy(parseerr, word[1]); ParseError(9, 0); /* "Nothing to (verb)..." */ return 0; } return true; } /* Go back for the next piece of the phrase */ pobjcount = 0; (*wordnum)--; goto NextLoop; } /* If we have a possible object or set of objects, go through the disqualification process, either to sort out any confusion, or even if there's only one possible object, to make sure it's available */ if (pobjcount >= 1) { bestavail = 0; for (k=0; k 1) { for (i=0; i 1 && Peek(grammaraddr)==ANYTHING_T) { for (i=0; i 1 && findobjectaddr) { for (k=0; k 1 && recursive_call) return -1; /* On the other hand, if we've managed to disqualify everything: */ if (pobjcount==0 && !speaking) { if (obj_match_state==5) { if (!starts_with_verb) /* "Better start with a verb..." */ ParseError(2, 0); else /* "That doesn't make any sense..." */ ParseError(6, 0); } else { if ((!domain) || domain != var[actor]) { if (Peek(grammaraddr)==ANYTHING_T) /* "...haven't seen..." */ ParseError(10, bestobj); else if (domain) /* "...don't see that there..." */ ParseError(14, bestobj); else /* "...don't see any..." */ ParseError(11, bestobj); } else ParseError(15, bestobj); /* "...don't have any..." */ } return 0; } else if (pobjcount==0) pobj = bestobj; /* If, after all this, there still exists some confusion about what object is meant: */ if (pobjcount > 1) { int wtemp; unsigned int wdtemp[MAXWORDS+1]; int tempobjlist[MAXWORDS+1], tempobjcount; struct pobject_structure temppobjlist[MAXPOBJECTS]; int tempobjfinish; int temppobjcount; int tempaddflag; char tempxverb = xverb; ParseError(8, 0); /* "Which...do you mean..." */ xverb = tempxverb; for (i=1; i<=words; i++) /* save word arrays */ wdtemp[i] = wd[i]; wtemp = words; tempobjfinish = objfinish; for (i=0; i<=objcount; i++) /* save object arrays */ tempobjlist[i] = objlist[i]; for (i=0; i<=pobjcount; i++) temppobjlist[i] = pobjlist[i]; tempobjcount = objcount; temppobjcount = pobjcount; tempaddflag = addflag; /* Get a new input and properly parse it; after all, it may be returned to MatchCommand() as a potentially valid command. */ GetCommand(); SeparateWords(); if (words==0) { ParseError(0, 1); return 0; } if (!Parse()) return 0; objfinish = words; /* Do we not care? i.e. is "any", "either", etc. given? */ if (!strcmp(word[1], "~any") && words==1) { for (k=0; k and word[] array */ strcpy(buffer, ""); for (i=1; i<=wtemp; i++) { strcat(buffer, GetWord(wdtemp[i])); strcat(buffer, " "); } SeparateWords(); /* Restore wd[] array after SeparateWords() */ for (i=1; i<=wtemp; i++) wd[i] = wdtemp[i]; words = wtemp; objfinish = tempobjfinish; /* Restore object lists */ for (i=0; i1 && i!=MULTI_T && i!=MULTIHELD_T && i!=MULTINOTHELD_T) { strcpy(parseerr, word[1]); /* "You can't...multiple objects." */ ParseError(3, 0); return 0; } /* Check objlist against original pobjlist */ for (i=0; i 1) { /* "...no such thing" */ ParseError(5, 0); return 0; } /* Check finally to make sure it's valid */ ResetFindObject(); if (!ValidObj(pobj)) return false; /* Finally--now add it or subtract it from the list, as appropriate: */ if (addflag==true) { if (pobjcount) { for (i=0; i is 2): */ if (obj_match_state==2 && domain != 0 && Parent(pobj) != domain && pobj != 0 && !speaking) { if (domain==var[player]) ParseError(15, pobj); /* "You don't have any..." */ else ParseError(14, pobj); /* "You don't see any...there..." */ return 0; } if (!strcmp(word[*wordnum], "~except")) { if (obj_match_state==1) { ParseError(7, 0); /* can't have multiple xobjects */ return 0; } addflag = false; } if ((strcmp(word[*wordnum], "~and") && strcmp(word[*wordnum], "~except")) || obj_match_state==5) { /* At the end yet? */ if ((*wordnum > words || word[*wordnum][0]=='\0') || *wordnum > objfinish) { if ((objcount > 0 && pobj != 0) || recursive_call || speaking) return true; else { /* No objects found */ strcpy(parseerr, word[1]); ParseError(9, 0); /* "Nothing to (verb)..." */ return 0; } } } /* Go back for the next object phrase */ pobjcount = 0; mobjs = 0; strcpy(parseerr, ""); goto NextLoop; } /* MATCHWORD */ int MatchWord(int *wordnum) { char num[18]; int i, p, t, flag, finish; unsigned int thissyntax, nextsyntax; if (wd[*wordnum]==0) return 0; switch ((t = Peek(grammaraddr))) { /* the verb ("*") */ case ASTERISK_T: (*wordnum)++; AdvanceGrammar(); return true; /* a non-specific dictionary word */ case WORD_T: if (obj_match_state==1) var[xobject] = wd[*wordnum]; else { var[object] = wd[*wordnum]; obj_match_state = 1; } object_is_number = true; (*wordnum)++; AdvanceGrammar(); return true; /* a specific dictionary entry */ case DICTENTRY_T: CheckWord: /* matches word */ if (wd[*wordnum]==PeekWord(grammaraddr + 1)) { (*wordnum)++; AdvanceGrammar(); while (Peek(grammaraddr)==9) grammaraddr += 4; return true; } else { /* if next word is a "/" */ if (Peek(grammaraddr + 3)==FORWARD_SLASH_T) { AdvanceGrammar(); /* this word */ AdvanceGrammar(); /* "/" */ goto CheckWord; } return 0; } /* alternative dictionary words */ case FORWARD_SLASH_T: grammaraddr++; return true; /* a number */ case NUMBER_T: if ((STARTS_AS_NUMBER(word[*wordnum])) && !strcmp(itoa(atoi(word[*wordnum]), num, 10), word[*wordnum])) { if (obj_match_state==1) var[xobject] = atoi(word[*wordnum]); else { var[object] = atoi(word[*wordnum]); obj_match_state = 1; } object_is_number = true; AdvanceGrammar(); (*wordnum)++; return true; } break; /* a string enclosed in quotes */ case STRING_T: if (parsestr[0]=='\"') { AdvanceGrammar(); (*wordnum)++; return true; } else return 0; default: { /* Some manifestation of an object (or objects) before domain is found, since is initially set to 0: */ if (obj_match_state==0) { if (Peek(grammaraddr)==HELD_T || Peek(grammaraddr)==MULTIHELD_T) odomain = var[actor]; obj_match_state = 1; /* since next set of object words must be the xobject */ objstart = *wordnum; objgrammar = grammaraddr; while (wd[*wordnum] != 0) { finish = *wordnum; /* Check what's coming up in case it's a dictionary word--which would override an object phrase. */ thissyntax = grammaraddr; AdvanceGrammar(); nextsyntax = grammaraddr; grammaraddr = thissyntax; /* dictionary word or string */ CheckWordorString: p = Peek(nextsyntax); if (p==DICTENTRY_T || p==STRING_T) { if ((PeekWord(nextsyntax + 1)==wd[*wordnum]) || (p==STRING_T && wd[*wordnum]==UNKNOWN_WORD)) { grammaraddr = nextsyntax; if (*wordnum != objstart) return true; else return false; } else if (Peek(nextsyntax + 3)==FORWARD_SLASH_T) { thissyntax = grammaraddr; grammaraddr = nextsyntax + 3; AdvanceGrammar(); nextsyntax = grammaraddr; grammaraddr = thissyntax; goto CheckWordorString; } } /* or a number */ else if ((p==NUMBER_T && STARTS_AS_NUMBER(word[*wordnum])) && !strcmp(word[*wordnum], itoa(atoi(word[*wordnum]), num, 10))) { grammaraddr = nextsyntax; if (*wordnum != objstart) return true; else return false; } /* Pass over any object words--they'll be matched specifically later in MatchCommand(). */ flag = 0; if (AnyObjWord(*wordnum)==1) { (*wordnum)++; flag = 1; } /* if "~and", "~all",... */ if (word[*wordnum][0]=='~') { int multicheck = Peek(grammaraddr); (*wordnum)++; flag = 1; /* multi or multi(something) */ if (multicheck != MULTI_T && multicheck != MULTIHELD_T && multicheck != MULTINOTHELD_T) { strcpy(parseerr, word[1]); /* "You can't...multiple objects." */ ParseError(3, 0); return 2; } } objfinish = finish; if (flag==0) return 0; } AdvanceGrammar(); return true; } /* hitting xobject */ else if (obj_match_state==1) { int temp_objfinish = objfinish; /* If we don't know the verbroutine, try to figure it out */ if (var[verbroutine]==0) { thissyntax = grammaraddr; AdvanceGrammar(); nextsyntax = grammaraddr; grammaraddr = thissyntax; if (Peek(nextsyntax)==ROUTINE_T) { var[verbroutine] = PeekWord(nextsyntax+1); } } p = Peek(grammaraddr); /* If the xobject is specifically a parent of the object(s) to be matched later: */ if (p==PARENT_T) domain = -1; /* Also deal with held xobjects */ t = domain; if (p==HELD_T || p==MULTIHELD_T) domain = var[actor]; /* Figure out where this xobject must end, as per grammar */ objfinish = -1; if (*wordnum < words) { thissyntax = grammaraddr; AdvanceGrammar(); nextsyntax = grammaraddr; grammaraddr = thissyntax; CheckXobjectFinish: p = Peek(nextsyntax); for (i=*wordnum+1; i<=words; i++) { if (p==DICTENTRY_T || p==STRING_T) { if ((PeekWord(nextsyntax + 1)==wd[i]) || (p==STRING_T && wd[i]==UNKNOWN_WORD)) { objfinish = i-1; break; } } else if (Peek(nextsyntax + 3)==FORWARD_SLASH_T) { thissyntax = grammaraddr; grammaraddr = nextsyntax + 3; AdvanceGrammar(); nextsyntax = grammaraddr; grammaraddr = thissyntax; goto CheckXobjectFinish; } else if ((p==NUMBER_T && STARTS_AS_NUMBER(word[*wordnum])) && !strcmp(word[*wordnum], itoa(atoi(word[*wordnum]), num, 10))) { objfinish = i; break; } else break; } } if (objfinish==-1) objfinish = words; /* Regardless, try to match the xobject */ recursive_call = false; if (MatchObject(&(*wordnum))==0) { objfinish = temp_objfinish; return 2; } else { objfinish = temp_objfinish; domain = 0; if (ValidObj(pobj)==false) return 2; domain = t; if (objcount==1) var[xobject] = objlist[0]; else var[xobject] = pobj; if (domain==-1) domain = var[xobject]; /* parent */ obj_match_state = 2; AdvanceGrammar(); /* Can't have multiple xobjects */ if (objcount > 1) { ParseError(7, 0); return 2; } objcount = 0; return true; } } } } return 0; } /* OBJWORDTYPE Returns true if the specified object has the specified word as an adjective or noun (as specified by type). */ int ObjWordType(int obj, unsigned int w, int type) { int j, num; unsigned int pa; pa = PropAddr(obj, type, 0); if (pa) { defseg = proptable; num = Peek(pa + 1); if (num==PROP_ROUTINE) { if ((unsigned int)GetProp(obj, type, 1, false)==w) { defseg = gameseg; return true; } } else { for (j=1; j<=num; j++) { if (PeekWord(pa + j * 2)==w) { defseg = gameseg; return true; } } } } defseg = gameseg; return false; } /* OBJWORD Returns if the word at dictionary address is an adjective of , or if it is a noun. */ int ObjWord(int obj, unsigned int w) { if ((obj_parselist) && !(obj_parselist[obj/8]&1<<(obj%8))) return 0; if (ObjWordType(obj, w, adjective)) return adjective; if (ObjWordType(obj, w, noun)) return noun; return 0; } /* PARSE Turns word[] into dictionary addresses stored in wd[]. Takes care of fingering illegal (unknown) words and doing alterations such as compounds, removals, and synonyms. */ int Parse() { char foundstring = 0; /* allow one unknown word/phrase */ int notfound_word = 0; int i, j, k, m; char num[33]; char tempword[81]; unsigned int period, comma; unsigned int synptr; period = FindWord("."); comma = FindWord(","); strcpy(parsestr, ""); /* for storing any unknown string */ parsed_number = 0; /* " " " parsed number */ for (i=1; i<=words; i++) /* find dictionary addresses */ { if (word[i][0]=='\"' && foundstring==0) { strcpy(parsestr, word[i]); foundstring = 1; wd[i] = UNKNOWN_WORD; } else { wd[i] = FindWord(word[i]); /* Numbers -32768 to 32767 are valid...*/ if (!strcmp(word[i], itoa(atoi(word[i]), num, 10))) { #if !defined (MATH_16BIT) if (atoi(word[i]) > 32767 || atoi(word[i]) < -32768) goto NotinDictionary; #endif parsed_number = atoi(word[i]); if (parseerr[0]=='\0') strcpy(parseerr, word[i]); } /* Otherwise it must be a dictionary entry */ else { /* If it's not in the dictionary */ if (wd[i]==UNKNOWN_WORD) { NotinDictionary: if (!notfound_word) { strcpy(parseerr, word[i]); strcpy(oops, word[i]); notfound_word = i; } } } } } /* Return here instead of immediately, so that we have a chance to load the rest of the recognized words into the word array */ if (notfound_word) { i = notfound_word; /* "...can't use the word..." */ ParseError(1, 0); strcpy(errbuf, ""); for (i=1; i<=words; i++) { strcat(errbuf, word[i]); if (i != words) strcat(errbuf, " "); } return 0; } wd[words+1] = 0; oopscount = 0; /* Do synonyms, removals, compounds, punctuation */ for (i=1; i<=words; i++) /* Look through words... */ { synptr = 2; for (j=1; j<=syncount; j++) /* ...and alterations */ { defseg = syntable; if (wd[i]==PeekWord(synptr + 1)) { switch (Peek(synptr)) { case 0: /* synonym */ { defseg = syntable; wd[i] = PeekWord(synptr + 3); m = strlen(GetWord(wd[i])) - strlen(word[i]); if (m) { if (m + (int)strlen(buffer) > 81) {strcpy(buffer, ""); words = 0; ParseError(0, 0); return 0;} for (k=words; k>i; k--) { strcpy(tempword, word[k]); word[k] += m; strcpy(word[k], tempword); } } strcpy(word[i], GetWord(wd[i])); i--; break; } case 1: /* removal */ { KillWord(i); i--; break; } case 2: /* compound */ { if (wd[i+1]==PeekWord(synptr+3)) { strcat(word[i], word[i+1]); wd[i] = FindWord(word[i]); KillWord(i+1); } break; } } goto NextSyn; } NextSyn: synptr += 5; } if (wd[i]==comma) { if (strcmp(word[i+1], "~and")) { word[i] = "~and"; wd[i] = FindWord("~and"); } else KillWord(i); } if (wd[i]==period) { wd[i] = 0; word[i] = ""; } } defseg = gameseg; if (strcmp(word[1], "~oops")) strcpy(oops, ""); if (words==0) { ParseError(0,0); /* What? */ return false; } return true; } /* PARSEERROR */ void ParseError(int e, int a) { int i, k, count; remaining = 0; xverb = true; if (e==5 && !strcmp(parseerr, "")) e = 6; if (parseerroraddr) { ret = 0; passlocal[0] = e; passlocal[1] = a; PassLocals(2); SetStackFrame(RESET_STACK_DEPTH, RUNROUTINE_BLOCK, 0, 0); #if defined (DEBUGGER) DebugRunRoutine((long)parseerroraddr*address_scale); #else RunRoutine((long)parseerroraddr*address_scale); #endif stack_depth = 0; retflag = 0; if (ret) { if (ret==2) reparse_everything = true; return; } } switch (e) { case 0: AP("What?"); break; case 1: sprintf(line, "You can't use the word \"%s\".", parseerr); AP(line); break; case 2: AP("Better start with a verb."); break; case 3: sprintf(line, "You can't %s multiple objects.", parseerr); AP(line); break; case 4: AP("Can't do that."); break; case 5: sprintf(line, "You haven't seen any \"%s\", nor are you likely to in the near future even if such a thing exists.", parseerr); AP(line); break; case 6: AP("That doesn't make any sense."); break; case 7: AP("You can't use multiple objects like that."); break; case 8: { sprintf(line, "Which %s do you mean, ", !parse_called_twice?parseerr:"exactly"); count = 1; for (k=0; k 2) strcat(line, ","); strcat(line, " or "); } else { if (count != 1) strcat(line, ", "); } if (GetProp(i, article, 1, 0)) { char *w = GetWord(GetProp(i, article, 1, 0)); /* Don't use "a" or "an" in listing */ /* if (!strcmp(w, "a") || !strcmp(w, "an")) strcat(line, "the "); else sprintf(line+strlen(line), "%s ", w); */ /* We'll just use "the" */ if (w) strcat(line, "the "); } strcat(line, Name(i)); count++; } } strcat(line, "?"); AP(line); break; } case 9: sprintf(line, "Nothing to %s.", parseerr); AP(line); break; case 10: AP("You haven't seen anything like that."); break; case 11: AP("You don't see that."); break; case 12: sprintf(line, "You can't do that with the %s.", Name(a)); AP(line); break; case 13: AP("You'll have to be a little more specific."); break; case 14: AP("You don't see that there."); break; case 15: AP("You don't have that."); break; case 16: AP("You'll have to make a mistake first."); break; case 17: AP("You can only correct one word at a time."); break; } } /* REMOVEWORD Deletes wd[a]. */ void RemoveWord(int a) { if (a > words) return; for (; a into the word[] array. Also does nifty things such as turning time values such as hh:mm into a single number (representing minutes from midnight). */ void SeparateWords(void) { char inquote = 0; char a[1025]; char b[2]; char w1[17], w2[17]; /* for time conversions */ char temp[17]; short n1, n2; /* must be 16 bits */ int bloc = 0; /* buffer location */ int i; /* First filter the line of any user-specified punctuation */ do { i = strcspn(buffer, punc_string); if (buffer[i]) buffer[i] = ' '; } while (buffer[i]); /* Begin the word-splitting proper: */ words = 1; /* Setup a blank string */ for (i=0; i MAXWORDS) words = MAXWORDS; word[words] = buffer + bloc; strcpy(word[words], ""); } if (b[0]=='\"' && inquote==0) { strcpy(buffer+bloc, b); bloc++; inquote = 1; } } else { if ((b[0]=='.' || b[0]==',') && inquote!=1) { if (word[words][0]!='\0') { bloc++; if (++words > MAXWORDS) words = MAXWORDS; } word[words] = buffer + bloc; strcpy(word[words], b); bloc += strlen(b) + 1; if (++words > MAXWORDS) words = MAXWORDS; word[words] = buffer + bloc; strcpy(word[words], ""); } else { strcpy(buffer+bloc, b); bloc++; } } } if (!strcmp(word[words], "")) words--; for (i=1; i<=words; i++) { /* Convert hours:minutes time to minutes only */ if (strcspn(word[i], ":")!=strlen(word[i]) && strlen(word[i])<=5) { strcpy(w1, Left(word[i], strcspn(word[i], ":"))); strcpy(w2, Right(word[i], strlen(word[i]) - strcspn(word[i], ":") - 1)); n1 = (short)atoi(w1); n2 = (short)atoi(w2); if (!strcmp(Left(w2, 1), "0")) strcpy(w2, Right(w2, strlen(w2) - 1)); /* If this is indeed a hh:mm time, write it back as the modified word, storing the original hh:mm in parse$: */ if (!strcmp(w1, itoa((int)n1, temp, 10)) && !strcmp(w2, itoa((int)n2, temp, 10)) && (n1 > 0 && n1 < 25) && (n2 >= 0 && n2 < 60)) { strcpy(parseerr, word[i]); itoa(n1 * 60 + n2, word[i], 10); } } } } /* SUBTRACTOBJ Removes object from objlist[], making all related adjustments. */ void SubtractObj(int obj) { int i, j; for (i=0; i as a possible contender for object disambiguation. */ void SubtractPossibleObject(int obj) { int i, j, last = 0; for (i=0; i is available, and add it to or subtract it from objlist[] accordingly. */ void TryObj(int obj) { unsigned int tempdomain; if ((obj_parselist) && !(obj_parselist[obj/8]&1<<(obj%8))) return; if (DomainObj(obj)) { tempdomain = domain; domain = 0; if (Available(obj, 0) && !InList(Parent(obj))) { AddObj(obj); } else { SubtractObj(obj); } domain = tempdomain; } } /* VALIDOBJ Checks first of all to see if an object is available, then checks if it meets all the qualifications demanded by the grammar syntax. */ int ValidObj(int obj) { int attr, nattr = 0; unsigned int addr; defseg = gameseg; if (!Available(obj, 0) && !speaking && (Peek(grammaraddr)!=OPEN_BRACKET_T || Peek(grammaraddr+1)!=ROUTINE_T)) { if (Peek(grammaraddr)==ANYTHING_T) ParseError(10, obj); /* "...haven't seen..." */ else ParseError(11, obj); /* "...don't see any..." */ return 0; } switch (Peek(grammaraddr)) { case OPEN_BRACKET_T: { if (Peek(grammaraddr+1)==ROUTINE_T) { addr = PeekWord(grammaraddr+2); ret = 0; passlocal[0] = obj; PassLocals(1); SetStackFrame(RESET_STACK_DEPTH, RUNROUTINE_BLOCK, 0, 0); #if defined (DEBUGGER) DebugRunRoutine((long)addr*address_scale); #else RunRoutine((long)addr*address_scale); #endif retflag = 0; /* If the routine doesn't return true, the object doesn't qualify. */ if (!ret) return (0); } else if (Peek(grammaraddr+1)==OBJECTNUM_T) { if (obj != (int)PeekWord(grammaraddr+2)) { strcpy(parseerr, ""); if (GetProp(obj, article, 1, 0)) strcpy(parseerr, "the "); strcat(parseerr, Name(obj)); /* "...can't do that with..." */ ParseError(12, obj); return 0; } } break; } case ATTR_T: case NOT_T: { if (Peek(grammaraddr)==NOT_T) nattr = 1; attr = Peek(grammaraddr + 1 + nattr); /* If the attribute match is not made, the object doesn't qualify. */ if (!TestAttribute(obj, attr, nattr)) { strcpy(parseerr, ""); if (GetProp(obj, article, 1, 0)) strcpy(parseerr, "the "); strcat(parseerr, Name(obj)); /* "...can't do that with..." */ ParseError(12, obj); return 0; } break; } } return true; }