From 1bbfcca229a3aa39854d495b0ce497d958d37b2e Mon Sep 17 00:00:00 2001 From: Paul Gilbert Date: Sat, 11 May 2019 13:59:44 +1000 Subject: GLK: HUGO: Add heparse --- engines/glk/hugo/hemisc.cpp | 10 +- engines/glk/hugo/heparse.cpp | 2579 +++++++++++++++++++++++++++++++++++++++ engines/glk/hugo/hugo.cpp | 4 +- engines/glk/hugo/hugo.h | 233 +++- engines/glk/hugo/hugo_defines.h | 2 + engines/glk/module.mk | 1 + 6 files changed, 2787 insertions(+), 42 deletions(-) create mode 100644 engines/glk/hugo/heparse.cpp diff --git a/engines/glk/hugo/hemisc.cpp b/engines/glk/hugo/hemisc.cpp index 9e560570d4..4a2ea2557a 100644 --- a/engines/glk/hugo/hemisc.cpp +++ b/engines/glk/hugo/hemisc.cpp @@ -977,13 +977,14 @@ char *Hugo::GetText(long textaddr) { return g; } -const char *Hugo::GetWord(unsigned int w) { - static const char *b; +char *Hugo::GetWord(unsigned int w) { + char *b; unsigned short a; + static char *EMPTY = ""; a = w; - if (a==0) return ""; + if (a==0) return EMPTY; if (a==PARSE_STRING_VAL) return parseerr; if (a==SERIAL_STRING_VAL) return serial; @@ -991,8 +992,7 @@ const char *Hugo::GetWord(unsigned int w) { /* bounds-checking to avoid some sort of memory arena error */ if ((long)(a+dicttable*16L) > codeend) { - b = ""; - return b; + return EMPTY; } defseg = dicttable; diff --git a/engines/glk/hugo/heparse.cpp b/engines/glk/hugo/heparse.cpp new file mode 100644 index 0000000000..2a70fed89d --- /dev/null +++ b/engines/glk/hugo/heparse.cpp @@ -0,0 +1,2579 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#include "glk/hugo/hugo.h" + +namespace Glk { +namespace Hugo { + +#define STARTS_AS_NUMBER(a) (((a[0]>='0' && a[0]<='9') || a[0]=='-')?1:0) + +void Hugo::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); + } + } +} + +void Hugo::AddObj(int obj) { + int i; + + for (i=0; i MAXOBJLIST) objcount = MAXOBJLIST; +} + +void Hugo::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; +} + +void Hugo::CallLibraryParse() { + 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 + } +} + +int Hugo::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; +} + +unsigned int Hugo::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 */ +} + +int Hugo::InList(int obj) { + int i; + + for (i=0; iwords) + return; + + for (i=a; i 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; i65535 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==1) + { + /* 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==1) + { + var[verbroutine] = PeekWord(grammaraddr + 1); + break; + } + } + + if (mw != 1) + { + if (mw==0) /* 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; +} + +bool Hugo::MatchObject(int *wordnum) { + char found_noun = false; + int i, j, k, m, flag; + int mobjs; unsigned int mobj[MAX_MOBJ]; + bool allmatch; + int roomloc; + int 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 false; + } + 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 false; + } + } + + 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 false; + } + 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 false; + } + } + + 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 false; + } + + 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 false; + } + + 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 false; + } + 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 true; + + + /* 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 false; + } + 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; + bool 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 false; + } + if (!Parse()) + return false; + + 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 false; + } + + /* Check objlist against original pobjlist */ + for (i=0; i 1) + { + /* "...no such thing" */ + ParseError(5, 0); + return false; + } + + /* 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 false; + } + + if (!strcmp(word[*wordnum], "~except")) + { + if (obj_match_state==1) + { + ParseError(7, 0); /* can't have multiple xobjects */ + return false; + } + 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 false; + } + } + } + + /* Go back for the next object phrase */ + pobjcount = 0; + mobjs = 0; + strcpy(parseerr, ""); + + goto NextLoop; +} + +int Hugo::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 1; + + /* 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 1; + + /* 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 1; + } + 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 1; + + /* 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 1; + } + break; + + /* a string enclosed in quotes */ + case STRING_T: + if (parsestr[0]=='\"') + { + AdvanceGrammar(); + (*wordnum)++; + return 1; + } + 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 1; + else + return 0; + } + 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 1; + else + return 0; + } + + /* 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 1; + } + + /* 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 1; + } + } + } + } + + return 0; +} + +int Hugo::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; +} + +int Hugo::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; +} + +int Hugo::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; +} + +void Hugo::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)) + { + const 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; + } +} + +void Hugo::RemoveWord(int a) { + if (a > words) + return; + + for (; a 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); + } + } + } +} + +void Hugo::SubtractObj(int obj) { + int i, j; + + for (i=0; i to objlist[], making all related adjustments. + */ + void AddObj(int obj); + + /** + * 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); + + /** + * Move the address in the grammar table past the current token. + */ + void AdvanceGrammar(); + + /** + * For when it's only necessary to know if word[wn] is an object word for any object, + * not a particular object. Returns 1 for an object word or -1 for a non-object word. + */ + int AnyObjWord(int wn); + + /** + * The non_grammar argument is true when called from a non-grammar function such as RunEvents(). + */ + int Available(int obj, char non_grammar); + + void CallLibraryParse(); + + /** + * Takes into account the preset domain for checking an object's presence; + * is 0, -1, or an object number.. + */ + int DomainObj(int obj); + + /** + * Returns the dictionary address of . + */ + unsigned int FindWord(char *a); + + /** + * Checks to see if is in objlist[]. + */ + int InList(int obj); + + /** + * Deletes word[a]. + */ + void KillWord(int a); + + /** + * Here, briefly, is how MatchCommand() works: + * + * 1. Match the verb. + * + * 2. If no match, check to see if the line begins with an object (character) + * and try to match it. + * + * 3. If found, try to match a syntax for that verb, including objects, dictionary words, + * numbers, attributes, and routines. If any objects are specified, skip over them for now, + * marking the start and finish. This is done mostly in MatchWord(). + * + * 4. Match the xobject, if there is one--via MatchObject(). + * + * 5. If all is well, return to match the objects that were previously skipped over, + * loading them into objlist[]. Once again, this is done by MatchObject(). + * + * (The reason the objects are initially skipped is because it may be necessary to know + * where to look for them--this may require knowing what the xobject is, if the syntax + * is something like: + * + * "get" "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(); + + /** + * 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 true on a recursive call to allow parsing to continue. + */ + bool MatchObject(int *wordnum); + + int MatchWord(int *wordnum); + + /** + * 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); + + /** + * Returns if the word at dictionary address is an adjective of , + * or if it is a noun. + */ + int ObjWord(int obj, unsigned int w); + + /** + * 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(); + + void ParseError(int e, int a); + + /** + * Deletes wd[a]. + */ + void RemoveWord(int a); + + /** + * Call FindObject(0, 0) to reset library's disambiguation mechanism. + */ + void ResetFindObject(); + + /** + * Splits 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(); + + /** + * Removes object from objlist[], making all related adjustments. + */ + void SubtractObj(int obj); + + /** + * Removes as a possible contender for object disambiguation. + */ + void SubtractPossibleObject(int obj); + + /** + * Called by MatchObject() to see if is available, and add it to or subtract + * it from objlist[] accordingly. + */ + void TryObj(int obj); + + /** + * 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); + + /**@}*/ + /** * \defgroup Miscellaneous * @{ @@ -735,6 +867,37 @@ private: void *hugo_blockalloc(size_t num) { return malloc(num); } void hugo_blockfree(void *block) { free(block); } + +#if defined (DEBUGGER) + int CheckinRange(uint v1, uint v2, const char *v3) { + // TODO: Where the heck is this actualy implemented in Gargoyle + return 1; + } + + /** + * Shorthand since many of these object functions may call CheckinRange() if the debugger + * is running and runtime_warnings is set. + */ + int CheckObjectRange(int obj); + + void DebugRunRoutine(long addr) {} + + void RuntimeWarning(const char *msg) {} + + void DebugMessageBox(const char *title, const char *msg) {} + + bool IsBreakpoint(long loc) const { return false; } + + const char *RoutineName(long loc) { return "Routine"; } + + void AddStringtoCodeWindow(const char *str) {} + + void SwitchtoDebugger() {} + + void DebuggerFatal(DEBUGGER_ERROR err) { error("Debugger error"); } + + void RecoverLastGood() {} +#endif public: /** * Constructor diff --git a/engines/glk/hugo/hugo_defines.h b/engines/glk/hugo/hugo_defines.h index 5609601ca8..5d69c847c2 100644 --- a/engines/glk/hugo/hugo_defines.h +++ b/engines/glk/hugo/hugo_defines.h @@ -41,8 +41,10 @@ namespace Hugo { #define MAX_DEBUG_LINE 256 #define MAX_OBJECT 999 #define MAX_PROPERTY 999 +#define MAX_MOBJ 16 /* maximum number of matchable object words */ #define MAXBUFFER 255 #define MAXUNDO 1024 + #define CHARWIDTH 1 #define STAT_UNAVAILABLE (-1) diff --git a/engines/glk/module.mk b/engines/glk/module.mk index 62237b6806..00f5e35d56 100644 --- a/engines/glk/module.mk +++ b/engines/glk/module.mk @@ -76,6 +76,7 @@ MODULE_OBJS := \ hugo/heglk.o \ hugo/hemisc.o \ hugo/heobject.o \ + hugo/heparse.o \ hugo/htokens.o \ hugo/hugo.o \ hugo/stringfn.o \ -- cgit v1.2.3