Sunday, March 17, 2019

Chapter 10. ORPHAN-MERGE: Fix What Is Broken (part 1)

  • Arguments: None
  • Return: TRUE if orphaned command is corrected, FALSE if unable to correct the orphaned command

10.1 Introduction

Now that the major parts of the command have been identified (if they exist) and stored in ITBL, PARSER will first see if the given command corrects an orphaned command, a previous command that had ambiguous or missing necessary information. Originally, only nouns could be ambiguous. So the clarifying token had be an adjective. If an adjective could refer to more than one object in a location, an error was given.
For example, the user enters:

        IGNITE CANDLE

in an empty room. An orphaned command will be created as no indirect object is given (the candle needs to be ignited with something). The game will respond that it does not know what the user wants to ignite the touch with. This could be correct by then typing:

        TORCH or WITH THE TORCH

A new command IGNITE CANDLE WITH TORCH would be created and replace the previous command TORCH (or WITH THE TORCH) and then processed by PARSER.
In another example, a room contains a red card, red flower, and blue flower. If the command was

GET FLOWER

it would be considered ambiguous as it could be the red or blue flower. This could be corrected by typing:

RED

which would then lead to a newly created command:

GET RED FLOWER

If the command was

GET BLUE

GET-OBJECT which matches the token with an object would be able to figure out you meant the blue flower as there is only one object with the BLUE adjective. If the command was

GET RED

then GET-OBJECT would display a missing noun error as the game will not create an orphaned command from an ambiguous adjective.
Starting with HGTG, ambiguous adjectives could be used to create an orphaned command be later clarified with a noun. So the previous example would prompt the game to ask you for clarification on which RED object. These examples could be corrected by typing just CARD or FLOWER. So a clarifying noun could also refer to more than one object in the same room which would then trigger another orphan command. The game would then use the new information to match a single specific object and process the newly completed command.

10.2 Fixing Orphans with ORPHAN-MERGE

ORPHAN-MERGE is called by PARSER if the P-OFLAG has been set in 2 possible situations:
  • SYNTAX-CHECK is in unable to match even one syntax entries for the given verb because of a missing object clause
  • GET-OBJECT is matches more than one object for the given noun, adjective, or GWIM bit
Regardless of the outcome of the routine call, P-OFLAG will be cleared. So the user only has one chance to supply missing or clarifying tokens to an orphaned command. The orphaned command data is stored in OTBL (a mirror of ITBL) and OCLAUSE (a mirror of LEXV).
ORPHAN-MERGE first validates the given input through various checks. Only the missing or clarifying token is required. If any extra information is given (like prepositions or verbs) are given, then they must match the ones in the orphaned command to be accepted. ORPHAN-MERGE checks two aspects of the given input:
  • Verb - To be a valid response to an orphan command, the new input must have the same verb as the orphaned command or must have no verb. If the verb is different, the new input is likely a new command, and ORPHAN-MERGE will return with FALSE.
  • Number of noun clause - Only one noun clause is allowed to clarify an ambiguous or supply a missing noun clause. If two are given in the new input, then ORPHAN-MERGE will return with FALSE.

ORPHAN-MERGE then checks if the orphaned command has a missing clause by looking for $01 in the starting address of the noun clauses in OTBL. If both object clauses have starting addresses equal to $01, then only the direct object clause is processed. PARSER will then attempt to process this partially completed command and again discover this partially incomplete command can satisfy a syntax entry. If this new command is again unable to be matched with a syntax, it will be orphaned with the indirect object clause missing, The user can then input another clarifying command for the indirect object clause.
Once a missing object clause is found, ORPHAN-MERGE will see ensure that the preposition in the given input (if entered) matches the one in the orphaned command (stored in OTBL). Finally, the routine will copy the start and end addresses of the clarifying noun clause (the single adjective) from LEXV into the appropriate start and end address in OTBL. If the missing object clause is the indirect one, the number of noun clauses will also be set to 2. ORPHAN-MERGE will then skip checking if the given input clarifies an ambiguous noun.

10.3 Clearing up Ambiguity

If no missing noun clause found in OTBL (start address with $01), the given input could clarify an ambiguous noun. ORPHAN-MERGE checks if P-ACLAUSE was set (by GET-OBJECTS) to the element of the clause’s start address in OTBL with the ambiguous noun ($06 for direct object and $08 for indirect object clause). ORPHAN-MERGE will then proceed:
  • Check that there is only one noun clause in the given input. If there are more, than clear P-ACLAUSE and return with a FALSE.
  • Loop through the tokens in the noun clause indicated by P-ACLAUSE and check their word types.
  • If the token’s word type is an adjective, then temporarily save the vocabulary address of that token. If an adjective has already been found, the new one will override the old one. So only the last given adjective is used for clarification.
  • If the token’s word type is a noun, then ORPHAN-MERGE ensures that the token is the same as the ambiguous noun (P-NAM) or is “ONE”. In the above example, “BLUE ONE” or “RED FLOWER” are both valid for the ambiguous FLOWER. It will then stop checking tokens and call ACLAUSE-WIN (to create a new noun clause)
  • All other word types or mismatched nouns will cause ORPHAN-MERGE to stop checking tokens and return FALSE.
  • If no nouns are given after checking all the tokens in the given noun clause, ORPHAN-MERGE will also call ACLAUSE-WIN using the last found adjective in the given noun clause.
  • If no adjective is given after checking all those tokens, ORPHAN-MERGE will return false.
Once any missing noun clause is replaced or an ambiguous word is clarified, ORPHAN-MERGE will copy the data from OTBL into ITBL for PARSER to continue to processing as if the complete command had just been entered. So an clarified orphaned command will use tokens located in LEXV and OCLAUSE.

10.4 ACLAUSE-WIN

  • Arguments: Vocabulary address for Adjective
  • Return: TRUE
ACLAUSE-WIN setups up parameters for CLAUSE-COPY using ACLAUSE (element number of a clause’s start address such as $06), ACLAUSE + 1 (element number of clause’s end address such as $07), and the clarifying adjective. By setting the parse table that CLAUSE-COPY should use (CC-TBL) to OTBL, CLAUSE-COPY will copy the tokens from OCLAUSE back to OCLAUSE and insert a clarifying adjective before the ambiguous noun (P-NAM). The routine will also ensure the P-NCN is set to the correct number of noun clauses. ACLAUSE is cleared to indicate the ambiguous noun was clarified.

10.5 Using CLAUSE-COPY

  • Arguments: Element number of start address of clause in ITBL, Element number of end address of clause in ITBL, INSERT adjective (optional)
  • Return: TRUE
CLAUSE-COPY copies tokens from LEXV to the end of OCLAUSE using the addresses in one of the parse table (ITBL or OTBL) referred by a global variable (official name is not known). CLAUSE-COPY is called with two arguments, the element numbers that identify the start and end addresses for the noun clause to copy. These element values are $06 and $07 for the direct object clause or $08 and $09 for the indirect object clause. It will then append the requested tokens to the end of OCLAUSE.
  • Find the offset to the end of OCLAUSE using the value in element 0.
  • Calculate the new start address of the orphaned noun clause by finding the end address of OCLAUSE as it is the start address for the newly appended tokens.
  • Store this new start address in the appropriate element (start address for DO or IO clause) in OTBL ($06 or $08).
  • CLAUSE-COPY will repeatedly call CLAUSE-ADD to copy the tokens located in the requested noun clause to the end of OCLAUSE. These new tokens have their byte pointer to the input buffer and length set to zero.
  • If a clarifying token (usually an adjective) is given while copying these tokens, CLAUSE-COPY will check if the copied token matches the current ambiguous token, P-ANAM (a vocabulary address for the ambiguous noun). If a match is found, CLAUSE-ADD is called with the vocabulary address of ths clarifying token and a new token with the clarifying token is added to OCLAUSE. Then the P-ANAM is also added to OCLAUSE with CLAUSE-ADD.
  • Once all the remaining tokens in the requested noun clause are appended to OCLAUSE, CLAUSE-COPY will calculate the new end address of OCLAUSE and save it into the proper element (end address of noun clause) in OTBL.
Starting with Zork 1, there was a bug with this setup. Each time an orphaned noun clause was created, it was appended to OCLAUSE. Since the end of OCLAUSE was never reset back to $00, it was possible OCLAUSE would grow outside of its 100 byte size limit and spill over into the memory locations of other variables.  This was correct with Zork 3 by setting the size of OCLAUSE to zero in the OPRHAN routine.

10.6 CLAUSE-ADD

  • Arguments: Vocabulary address to token
  • Return: True
CLAUSE-ADD takes the given Vocabulary address and creates a new token add the end of OCLAUSE. Originally, the destination token buffer defaulted to OCLAUSE. In Moonmist, the P-CCTBL was used to stored the address of the destination token buffer in element 2. More information about P-CCTBL in Section 10.10. In Bureaucracy (and later Beyond Zork), the address of the destination token buffer was passed as the 2nd argument instead of using P-CCTBL.

Friday, March 15, 2019

Chapter 9. CLAUSE: Find the Noun Clause Boundaries

  • Arguments: Start address of clause, Preposition number of clause, Vocabulary address of current token
  • Return: Address to start of clause, -1 if no tokens are left to process, or FALSE if error in clause syntax

9.1 Introduction

CLAUSE will find the end address of a noun clause and saves the start and end addresses for this clause along with the preposition information in ITBL. P-NCN (noun clause number) indicates if the direct (1) or indirect (2) object clause is being processed. Specific locations in ITBL for the extracted information to be stored are calculated depending on P-NCN.

Direct Object (Noun 1) Clause
Indirect Object (Noun 2)
Clause
Preposition Number
Word 2
Word 4
Vocabulary Address of Preposition
Word 3
Word 5
Address of First Token (Start)
Word 6
Word 8
Address after Last Token (End)
Word 7
Word 9

9.2 How it screens...

CLAUSE loops through tokens in LEXV as long as the given tokens are part of a valid syntax for a noun clause. Briefly, the routine will
  1. Save any preposition information (number and address) if it exists into the proper locations in ITBL.
  1. The preposition information is usually passed as an argument. CLAUSE will then find the Vocabulary address of the preposition.
  2. If no preposition is given or exists, $00 is saved as the preposition number and address
  1. Save the start address of the noun clause into the proper location in ITBL.
  2. Attempt to get the next token to process as well.
  3. Loop through the tokens in LEXV as long as the given tokens are part of a valid syntax for a noun clause (see rules below)
Once the routine stopped checking for tokens, it can return three possible results: FALSE if there was a syntax error, -1 if no more tokens left to process, or offset to the last token in the newly found clause. The address after the end of the newly found clause will be saved in the proper location in ITBL for the end address of the clause.

Type
Example
Action
Unmatched
Display error & return
Conjunction (“and” or “,”)
Eat apple and orange
Set ANDFLAG & continue
Quantity (“all” or “one”)
Take all bananas
Continue
Quantity (“all” or “one”) + “of”
Burn one of the letters
Skip “of” token & continue
End-of-command
“then” or “.”
Throw hammer then go west
Save end address of current noun clause and return
Preposition (not the first token in the clause)
Put letter inside mailbox
Save end address of current noun clause and return
Noun (also an Adjective) + Noun
Drop gold coin
Continue
Noun and ANDFLAG set
Eat apple and orange and grape
Clear ANDFLAG & continue
Noun and ANDFLAG clear and next token is an exception (“but” or “exception”) or conjunction token
Eat apple and orange
Eat all fruit except banana
Continue
Noun
Turn statue
Save end address of current noun clause & return
Adjective
Read brown book
Continue
Special (not Conjunction, Quantity, or End-of-command)
Push the button
Continue
All others (verbs or direction) and ANDFLAG set
Ignite red match and go east
Change conjunction token to “then” and go back 2 tokens to process again*
All others (verbs or direction) and ANDFLAG clear
Cut rope go north
Display “Can’t Use” error & return
*For example:
        get candle and eat apple
                     ^
is converted to:
        get candle then eat apple
                 ^                
with CLAUSE going back to the conjunction token to reprocess the clause. The routine will recognize “then” as an end-of-command token, marking the end of a clause.

9.3 Update: New Matches…
Later Infocom games added more acceptable token combination for clauses which caused CLAUSE to also grow. Outside of specialized tokens used in specific games (such as spell names in Spellbreaker), the new and improved CLAUSE mainly became more sophisticated on deciding what part of speech a token was being based upon its context with other tokens. The major additional rules created by Infocom are below:
Type
Example
Action
Game
Articles ("the","a","an")
Eat an apple
Skip over token & continue
Deadline
Unmatched
and NUMBER? is true
Take 2 candles
Unmatched token changed to “intnum”, global var set to value, & continue
Deadline
“of” and verb = “accuse”
Accuse Bob of murder
Change “of” to “with” & continue
Deadline
“.” and previous token is a title (“mrs”,”mr”,”ms”, “dr”, “st”)
Ask Mr. Smith about paper
Continue
Deadline, LGOP
Preposition (first token checked)
Throw out ball
Continue
Zork 1-R88
Preposition (prep value already given in argument)
Skip over token & continue
LGOP
Preposition (can also be an adjective) and a prep has already been give
Write on back side
Assume preposition is an adjective and continue
Bureaucracy
Noun (can also be adjective) and next token is noun or adj or direction
Drop gold coin
Take copper hot rod
Assume noun is an adjective & Continue
HGTG, Moonmist
Adjective AND P-MERGED/ P-OFLAG set or verb given
Green
Get Green
Continue
Zork 1-R88
Special (not Conjunction, Quantity, or End-of-command) AND P-MERGED/ P-OFLAG set or verb given
red
Get red
Continue
Zork 1-R88
All others (verb, direction) and ANDFLAG set and verb given
Ignite red match and go east
Change conjunction token to “then” and go back 2 tokens & continue
Seastalker
Profanity, interrogatives
Eat banana who
Display error message & stop
LGOP, Trinity

Monday, March 11, 2019

Chapter 8. PARSER: New Commands and Routines (Part 2)

8.5 OOPS!

The OOPS command was an important innovation in the era before copy and paste were invented. Previously any commands with errors would have to be completely re-typed with the correction. This was especially painful for long or multiple commands. The user could correct any errors with
OOPS <replacement token>
If an unknown token is found, various values are calculated and stored in OOPS-TABLE:
Word 0
Word 1
Word 2
Word 3
Offset to unknown token in AGAIN-LEXV
Offset to start of command with unknown token in AGAIN-LEXV
Length of all token data  in command with the unknown token
Offset to byte after the end of OOPS-INBUF
PARSER will grab the Vocabulary address of the replacement token (after OOPS) and replace it with the empty address in the unknown token’s entry in AGAIN-LEXV. Only the first token after OOPS is checked. An error message is display if more tokens are given. INBUF-ADD is called to appended the replacement token to the end of the OOPS-INBUF. That routine will also store the replacement token’s length and updated pointer in OOPS-INBUF in the unknown token’s entry in AGAIN-LEXV. Finally the  modified OOPS-INBUF and AGAIN-LEXV are copied back into the INBUF and LEXV, respectively. PARSER then resumes parsing the command as it normally does. If there is another unknown word error in this just correct command, another OOPS can be used to correct the next error. This can go on until all the errors are fixed.
This feature has been present in all version 3 games since Sorcerer-R18, and all version 4 and 5 games since its introduction.

8.6 INBUF-ADD

  • Arguments: length of replacement token, ptr of replacement token in INBUF, offset of unknown token in OOPS-INBUF
  • Returns: TRUE

INBUF-ADD is slightly more sophisticated. The characters for the referred token in the first two arguments in appended to the end of OOPS-INBUF. The pointer to the end of OOPS-INBUF is retrieved from the OOPS-TABLE (or calculated using the data from the last token if it is not in the OOPS-TABLE). The length of this replacement token and pointer to it in OOPS-INBUF are copied back into the unknown token’s entry in AGAIN-LEXV.

8.7 UNDO

Finally an UNDO command was introduced with Beyond Zork which would restore the game state before the last command was executed. While this capability was available since version 1 of Z-machines, it was never implemented because of the need for more internal memory (a limited quantity back then) or having to constantly save the data onto an external memory storage device, likely a disk drive, which would slow the execution of the game down. But the requirements of XZIP called for ZIP emulators with higher memory capacities, the necessary game state data could be saved into a different part of the internal memory. Only Beyond Zork and Sherlock had this function.
The saving of the current game state was done in the PARSER routine after the check for a GO command. The save_undo operation is used to copy the contents of dynamic memory (everything before the Syntax data) which is essentially the header information, all OBJECT data, global variables, tables, and buffers. Also the call stack is saved. If the player uses the UNDO command, it would call a separate routine that would attempt to restore the saved gate state data  and continue with the execution of the command after the save_undo operation in PARSER. If an error occurs, the appropriate error message is displayed.

8.8 Update: Other Minor Changes in PARSER

EZIP and XZIP games only added slight modifications to PARSER. Most of these revolved around setting or clearing certain global variables such as XNAM. Others would search for unimportant tokens such as “please” and skip over them.

8.9 Update: ReplaceToken routine


With the advent of  the AGAIN command in AMFV and its use of the AGAIN-LEXV buffer, the replacing of tokens in PARSER and CLAUSE required changes in both those buffers. So a new routine was created, ReplaceToken. But it also including a way to copy the length and pointer information for the token before the replaced one into the location of the replaced one. The dictionary address for the replaced token will then be modified as normal.