--------------------------------------------------------------------------- Pname.h, Version 1.1, 10 May 2001, copyright 2001, by Neil Cerutti (cerutti@together.net) This version of pname.h is recommended for use only with version 6/10 of the Inform Library. This is the documentation for pname.h, a package which, by supplementing the object name recognition code in the Inform Library, greatly reduces the need to write parse_name routines. It contains the following sections: INTRODUCTION INSTALLATION and USAGE Q&A OPERATORS PARSING TECHNICAL NOTES RESERVED WORDS DISTRIBUTION NO WARRANTY SPECIAL THANKS VERSION HISTORY --------------------------------------------------------------------------- INTRODUCTION If your game includes objects with similar names occuring in the same place (for example, a Kitchen containing a JUICY ORANGE and some ORANGE JUICE), you'll be familiar with the problem of disambiguation: EXAMINE JUICY and EXAMINE JUICE describe respectively the fruit and the beverage, but EXAMINE ORANGE prompts for a more precise specification of which object is meant. To avoid irritating the player -- who may consider it 'obvious' that in this case the fruit is the intended target -- you are then forced to replace these objects' name properties (with their simple lists of dictionary words) by more complex parse_name properties; these comprise customized routines which can be tricky to write and debug. The pname.h package defines a new object property, pname (short for phrase name), with a similar look and feel to the standard name property: both contain a list of dictionary words. However, in a pname property the order of the words is significant, and special operators '.p' '.or' and '.x' enable you to embed some intelligence into the list. In most cases where the standard name property isn't enough, you can now just replace it with a pname property, rather than write a parse_name property routine. For example, to solve the problem above, all you need is: Object example_fruit "juicy orange" with pname '.x' 'juicy' 'orange', ... ; Object example_beverage "orange juice" with pname '.x' 'orange' 'juice', ... ; That is, a pname property is like a name property that has the enhanced capability of defining 'phrases' -- sequences of words -- that increase an object's likelihood of being selected when the player types matching words. In doing so, it provides an alternative to parse_name which is quick, flexible, and easy to use. --------------------------------------------------------------------------- INSTALLATION and USAGE To incorporate this package into your program, do three things: 1. Add four lines near the head of the program (before you include Parser.h). Replace MakeMatch; Replace Identical; Replace NounDomain; Replace TryGivenObject; 2. Include the pname.h header just after you include Parser.h. Include "Parser"; Include "pname.h"; 3. Add pname properties to those objects which require phrase recognition. Here's an example of an Inform program which uses this package: Constant Story "TEST STORY"; Constant Headline "^This is only a test^^"; Replace MakeMatch; ! /---- add these lines before Parser.h Replace Identical; ! | Replace NounDomain; ! | Replace TryGivenObject; !<' Include "Parser"; Include "pname.h"; ! <---- add this line after Parser.h Include "VerbLib"; ... Object shed "garden shed" with description "The wooden shed...", pname '.x' 'wooden' '.x' 'garden' 'shed', ... ; Object path "garden path" with description "The stony path...", pname '.x' 'stony' '.x' 'garden' 'path', ... ; Object garden "garden" with description "The garden...", name 'garden', ... ; ... A pname property defines one or more phrases that can refer to the given object, using dictionary words and simple operators. For instance, consider the pname declaration for the garden shed: pname '.x' 'wooden' '.x' 'garden' 'shed' The "garden shed" object's pname property contains one phrase in which the words 'wooden' and 'garden' are optional and 'shed' is mandatory. During parsing, the property generates a 'phrase match' -- a signal which increases its chance of being picked without the need for a disambiguation prompt -- in response to the inputs: WOODEN GARDEN SHED, WOODEN SHED, GARDEN SHED, and SHED. (For that matter, it would also generate matches for GARDEN GARDEN WOODEN WOODEN WOODEN GARDEN SHED, but not for TIMBER SHED or SHED WOODEN GARDEN.) --------------------------------------------------------------------------- Q&A Q: In what ways is the pname property different from the name property? A: In a pname property, the order in which the words appear makes a difference during the disambiguation process. In the standard name property, word order doesn't matter. In addition, you can include operators in your pname properties to help define them more precisely. The name property is always completely general. Q: Can't I already do this sort of thing with a parse_name routine? A: Not exactly, not as easily, not as generally, and not as quickly. (Also, don't worry if you don't understand parse_name routines... you won't have much need of them if you use pname.h.) For example, suppose your game contains some coffee and a coffee table: Object coffee "coffee" with name 'coffee', description "It's piping hot, with a stern warning label written on the side concerning debilitating tongue burns.", ... ; Object coffee_table "coffee table" with name 'coffee' 'table', description "It's rectangular, made of black marble and has an overstuffed magazine-rack underneath.", ... ; However, disambiguation fails if the coffee and the coffee table are ever in scope together: You can see a coffe table and some coffee here. >EXAMINE COFFEE Which do you mean, the coffee or the coffee table? >COFFEE Which do you mean, the coffee or the coffee table? The coffee has become impossible to refer to! The traditional solution to this problem is to put a restrictive parse_name routine on the coffee table, which forces the game never to recognize the word COFFEE alone as referring to the table. Object coffee_table "coffee table" with parse_name [ wd; wd = NextWord(); if (wd == 'coffee' && NextWord() == 'table') return 2; else if (wd == 'table') return 1; else return 0; ], description "It's rectangular... ", ... ; This solution works great when both objects are in scope: You can see a coffee table and some coffee here. >EXAMINE COFFEE It's piping hot... >EXAMINE COFFEE TABLE It's rectangular... But there's still a trap. This doesn't work when the coffee table is in scope but the coffee is not, causing frustration to players when perfectly unambiguous input is rejected: You can see a coffee table here. >EXAMINE COFFEE You can't see any such thing. In this case, the parse_name routine is too restrictive. A more flexible routine would know that, when the coffee is not in scope, it's okay for COFFEE alone to refer to the coffee table. You can fix this problem by enhancing your parse_name, but you'll affect performance: Object coffee_table "coffee table" with name 'coffee' 'table', parse_name [ wd; if (~~TestScope(coffee)) return -1; ! use name prop instead wd = NextWord(); if (wd == 'coffee' && NextWord() == 'table') return 2; else if (wd == 'table') return 1; else return 0; ], description "It's rectangular... ", ... ; Here, the table's parse_name routine applies only when the coffee isn't in scope. However, calling TestScope() is computationally expensive; calling it from within a parse_name routine is very expensive, and doing so will noticeably slow down your game even on a fast computer. The situation, already messy, becomes worse if another ambiguous object (for example, a coffee table book) is added to the game; you're then forced to modify all your working parse_name routines. But, help is at hand! Using pname.h, you can simply code: Object coffee "coffee" with name 'coffee', description "It's piping hot... ", ... ; Object coffee_table "coffee table" with pname '.x' 'coffee' 'table', description "It's rectangular... ", ... ; Object coffee_table_book "coffee table book" with pname '.x' 'coffee' '.x' 'table' 'book', description "Hard covered and beautifully decorated,... ", ... ; Q: Cool! So I should use pname properties on all my objects, right? A: That's not necessary. You should use pname properties only when disambiguation might become a problem, such as the coffee table situation described above. There is no advantage to using them when Inform's normal system of name recognition is sufficient. For example, if you have a coffee mug and a litter bug in your game, the name property is capable of distinguishing between them and using a pname property would be a waste. Q: I defined a pname property for the black rod from Adventure: Object black_rod "black rod" with pname 'black' 'rod', ... ; Since both words 'black' and 'rod', are mandatory in that phrase, the single word BLACK and the phrase ROD BLACK will never refer to my object. Is that right? A: It depends; the object could still end up being the most likely match in scope. Remember that the pname property acts only as an _aid_ to disambiguation. If the player's input isn't ambiguous then the parser selects the black rod even if the phrase 'black' 'rod' isn't matched by the input. The phrase matcher in pname.h is not restrictive; rather, it applies bonuses during the disambiguation process for phrases that are matched. Q: Oh. So what's a phrase, and what's a phrase match? A: A phrase is a string of dictionary words in a pname property, possibly containing operators. More than one phrase can appear in a single pname property if they are separated by the '.p' phrase separator operator. A phrase match results when the words input by the player match one of the phrases specified in a pname property. If there is a phrase match, the object receives a bonus during disambiguation. Q: Is pname.h Glulx compatible? In other words, Can I use pname.h in an Inform program designed to be compiled for the new Glulx virtual machine? A: Yes. Q: OK. I'm convinced that pname.h will make my life easier. But what are the disadvantages of using pname.h in my program, if any? A: The biggest disadvantage is that pname.h replaces several important routines that are integral to the Inform Library. You are effectively locked into using a specific Library version with pname.h, and any modifications you have made to those routines in your copy of the Library will be overridden by pname.h. This version of pname.h is designed for the Library version shown at the head of this file, and is very likely to be incompatible with past and future versions. A smaller disadvantage is that pname.h doesn't quite live up to its promise of obviating parse_name routines. You will probably still need to write a few of them; see the TECHNICAL NOTES section below. An even smaller disadvantage is that it uses up one of Inform's limited stock of object attributes. --------------------------------------------------------------------------- OPERATORS The pname property takes a list of dictionary words in single quotes, in exactly the same manner as name. The primary difference is that some of those words can be the pname operators '.p', '.or', and '.x' which are defined here. '.p' phrase separator operator Used to separate phrase declarations in a pname property. Everything after it and up to the end of the property or the next phrase separator is interpreted as a single phrase. All pname properties begin with an implied '.p' if no phrase separator is provided. Using this operator, you can declare more than one phrase for the same object. For example: pname 'old' 'brass' 'lantern' '.p' 'shining' 'light' defines two phrases: 'old' 'brass' 'lantern' and 'shining' 'light'. It would generate phrase matches for the inputs OLD BRASS LANTERN and SHINING LIGHT, but not for BRASS LIGHT or SHINING LANTERN. '.or' or operator A binary operator used in phrases to show that certain words in the phrase might be substituted for other words. For example: pname 'soda' '.or' 'pop' 'machine' would generate phrase matches for the inputs SODA MACHINE and POP MACHINE. You can string together as many words as you like with this operator. For example: pname 'soda' '.or' 'pop' '.or' 'bubbly' '.or' 'coke' 'machine' A phrase match would then result from all of the following inputs: SODA MACHINE, POP MACHINE, BUBBLY MACHINE and COKE MACHINE. But there would be no phrase match for any of: MACHINE, SODA SODA MACHINE, SODA POP MACHINE, SODA BUBBLY MACHINE, MACHINE SODA or COKE. '.x' optional operator A unary prefix operator, which introduces a dictionary word that is optional in a phrase. The first dictionary word to the right of a '.x' operator is interpreted as optional. For example: pname 'soda' '.x' 'machine' 'button' denotes that the word 'machine' is optional in the above phrase. It would generate a phrase match for the inputs SODA MACHINE BUTTON and SODA BUTTON. There would be now match with MACHINE BUTTON, because 'soda' is not marked as optional. You can string together as many optional words as you like; all input words that match any of the words in the string of options will be matched, in any order. For example: pname '.x' 'black' '.x' 'swimming' 'trunks' This would generate a phrase match for all of the following inputs: TRUNKS, BLACK TRUNKS, BLACK SWIMMING TRUNKS, SWIMMING TRUNKS, SWIMMING BLACK TRUNKS and SWIMMING SWIMMING BLACK BLACK SWIMMING TRUNKS. No phrase match results from any of: BLACK, SWIMMING or TREE TRUNKS. A pname property that contained all optional words would act just like a standard name property. --------------------------------------------------------------------------- PARSING The pname.h parser counts the number of words in the input that appear in the pname property of the given object. It also records whether or not the input happens to match any of the phrases specified for the object. If the number of total words that match is equal to the longest possible phrase match, then the object is marked as having matched a phrase in the input. An object with a name property is treated as if it were a pname property in which all the words are optional -- so every match results in a phrase match. If an object has both a name property and a pname property, pname effectively masks the existence of the name property from the parser. The same is true for parse_name routines. If you have a parse_name routine on an object with a pname property, the parse_name will not be called during parsing. Next, during an early phase of Inform's disambiguation process, from all the objects that match at least one word in the input, only the ones that match the most consecutive words in the input are retained. Finally, among the objects that matched the most words, only the ones that generated a phrase match are retained (unless none of them generated a phrase match, in which case they are all retained). Thus, it's possible for an object that generates no phrase matches to be selected as the most likely. In the coffee table example in the Q&A section above, when the coffee was not in scope and the player specified only COFFEE, even though the single word COFFEE does not produce a phrase match for the coffee table, the coffee table is still deemed the most likely object by the parser because it matched a word in the input. Here's a longer example and a more detailed explanation. The starting location of _Uncle Zebulon's Will_ by Magnus Olsson has some troubling parsing problems. There's a garden, a garden path, and a garden shed all in scope at once. Using pname phrases, you can solve the problem by making 'garden' optional for the garden path and the garden shed: Object garden "garden" with name 'garden', description "The garden...", ... ; Object path "garden path" with pname '.x' 'garden' 'path', description "The path...", ... ; Object shed "garden shed" with pname '.x' 'garden' 'shed', description "The shed...", ... ; The following table shows phrase matches and word matches for several possible inputs and explains which object results. Words Input Object Matched Phrase match? ----------- ------- ------- ------------------------ GARDEN garden: 1 yes path: 1 no shed: 1 no ------------------- result: garden SHED garden: 0 no path: 0 no shed: 1 no ------------------- result: shed GARDEN SHED garden: 1 yes path: 0 no shed: 2 yes ------------------- result: shed [it matched the most words - on that basis, it would have won even if it had not generated a phrase match] PATH garden: 0 no path: 1 yes shed: 0 no ------------------- result: path GARDEN PATH garden: 1 yes path: 2 yes shed: 1 no ------------------- result: path --------------------------------------------------------------------------- TECHNICAL NOTES I recommend you use only the latest version of the Inform Library. You may use either the standard version by Graham Nelson or Andrew Plotkin's cross-platform port for compiling Glulx files. The pname parser considers as indistinguishable any two objects that have equivalent name properties and duplicate pname properties. To be indistinguishable, the name property of the two objects must be equivalent: every word in one must appear somewhere in the other (See Nelson, Graham: _The Inform Designer's Manual_, 3rd Ed, Section 25). pname properties must be identical. The pname property is additive. That means that pname properties inherit the pname property of their parent class. The way this works in practice is: the pname properties of the instance and its parent classes are concatenated into a list and that list is assigned to the instance as its pname property. When a name property is created through inheritance, the order in which the properties are concatenated doesn't matter. However, since the order of the words in a pname property is important, you need to understand the order of inheritance. Inheritance start with the instance and works its way back up through its parent classes. Class Rod with pname 'rod' '.or' 'wand'; Rod black_rod with pname '.x' 'black; Rod blue_rod with pname '.x' 'blue'; After compilation, the black rod's pname property is '.x' 'black' 'rod' '.or' 'wand' and the blue rod's is '.x' 'blue' 'rod' '.or' 'wand'. For multiple inheritance, the order in which the list is created is equivalent to the order the classes are declared in the class clause of the inheriting object. Class Rod with pname 'rod'; Class Wand with pname 'wand'; Object black_rod class Rod Wand with pname '.x' 'black'; In this case the pname property of the black_rod is '.x' 'black' 'rod' 'wand' which is probably not what you wanted! You have to be careful. If you don't want pname properties of Class objects to have a chance of interfering with pname properties of its intances, you can include a phrase separator operator in the declaration. Class Rod with pname '.p' 'rod'; Class Wand with pname '.p' 'wand'; Object black_rod class Rod Wand with pname 'black'; The black_rod, after compilation, will have a pname property of 'black' '.p' 'rod' '.p' 'wand', in other words, a pname property which contains three one-word phrases. If a class contains a name property then it will be masked by the existence of any instances that contain a pname property. pname_verify will print warnings in this case (see DEBUGGING). You may use plural flagged dictionary words in your pname properties, and they will be recognized by the pname parser. A plural dictionary word is defined with a //p on the end (for example, 'dogs//p'). If the parser sees one, it knows that the player may be referring to more than one object. (See: Nelson, Section 25.) There are some uses for parse_name routines that pname.h doesn't cover. A parse_name must be used to dynamically change the name of an object during the course of a game. In addition, a parse_name routine may be needed by a class of similar objects in order to be explicit about what is indistinguishable and what isn't. As noted in the PARSING section above, do not try to have a parse_name and a pname in the same object. They do not coexist happily. --------------------------------------------------------------------------- DEBUGGING This package contains a useful set of debugging diagnostic statements for times when thorny parsing issues arise (they were also of great help while debugging this package!); these are compiled only if DEBUG is defined. The parser trace level must be set to 7 or higher for full phrase parsing diagnostics to print (See: Nelson, Section 30), although trace level 5 is enough to show which objects produced phrase matches. Due to speed and algorithmic simplicity concerns, pname.h does not do error checking for bad pname declarations during parsing. To ameliorate this shortcoming, pname.h provides a pname_verify routine. When DEBUG is defined, you may call pname_verify() in your Initialise() routine to verify the pname properties in your objects. Here's what I suggest: [ Initialise; #ifdef DEBUG; pname_verify(); #endif; ! etc... ]; pname_verify loops over all the game objects and reports errors and warnings for pname properties it finds suspect. The game is aborted if any errors are reported. pname_verify report a warning if it finds 'x', 'p', or 'or' in a pname property, since they are most likely typos for '.x', '.p' and '.or', respectively. pname_verify also reports a warning for objects with pname property and a name or parse_name property, because a pname property masks both the parse_name and name properties from the parser. You can call pname_verify with 'true' as its argument to suppress these warning messages: pname_verify(true); ! suppress warnings --------------------------------------------------------------------------- RESERVED WORDS In addition to the identifiers reserved by Inform and the standard Library, pname.h defines the following. You mustn't use any of the following words in a program that Includes pname.h, except as documented here. Globals: matches_in_match_list Constants: PHRASE_OP '.p' OR_OP '.or' OPT_OP '.x' You may use any of these constants in your program but you must not try to define them yourself. Routines: _pn_pname _pn_matchPhrase _pn_inpWord _pn_inpLen _pn_TryGivenObject additionally, if DEBUG is defined: pname_verify _pn_printInpWord _pn_printPhrase _pn_isOp _pn_error _pn_warning You shouldn't call any of the above routines in your program except for pname_verify. Attributes: phrase_matched This is used internally by the pname parser to mark objects for which a phrase match has been detected. Standard Library routines: MakeMatch Identical NounDomain TryGivenObject These Library routines are replaced in pname.h so don't try to replace them yourself. If you want to hack one of these routine in a program that includes pname.h you will need to hack pname.h instead. --------------------------------------------------------------------------- DISTRIBUTION You may use the header file pname.h in any Inform program. You may distribute source code which uses pname.h, but you may not distribute pname.h with your source code except as described below. The header file pname.h itself may only be distributed if this documentation file, pname.txt, is included and pname.h is not modified in any way. This documentation file, 'pname.txt' may not be altered without the permission of the author, Neil Cerutti <cerutti@together.net>, except if the contents remain unchanged. It is permissible to adapt it to a different file format, e.g., HTML, PDF, PS, however and I don't consider such adaptation to be an alteration. You may distribute compiled game files which include pname.h. You may also distribute compiled game files which include an altered version of pname.h. --------------------------------------------------------------------------- NO WARRANTY Because this software is distributed without charge, you assume the entire responsibility for determining whether the program fits your needs and whether it is correct and/or complete. Neil Cerutti, and any distributors of this software specifically disclaim any and all warranties, expressed or implied, including but not limited to implied warranties of merchantability and fitness for a particular purpose, with regard to this software. In no event will the author or any distributor of this software be liable to you for any damages, including lost profits, lost savings, or other incidental or consequential damages arising out of the use or inability to use this software, even if any of these parties has been advised of the possibility of such damages, or for any claim by any other party. --------------------------------------------------------------------------- SPECIAL THANKS Thanks to Roger Firth who submitted an unsolicited re-organization of this file that helped transform it from a specification into documentation. His rewrites were invaluable in writing this documentation. Thanks to Marnie Parker and David Cornelson, who both looked at very early drafts of this document and gave good advice and asked good questions, which helped streamline and clarify the design of pname.h, which was originally much more complex. Thanks to Andrew Plotkin for donating the code for the cross-platform (Z and Glulx) versions of Identical, TryGivenObject, MakeMatch and ParseNoun from his cross-platform port of Inform Library 6/10. --------------------------------------------------------------------------- VERSION HISTORY 1.0 18 April 2001 Initial release 1.1 10 May 2001 o Fixed bug that caused to the parser to never generate a phrase match for a phrase in which an optional word was followed by the same consecutive word, or in an '.or' clause, e.g., '.x' 'card' 'card'. The phrase '.x' 'card' 'card' will now generate a 1 word phrase match for the input CARD and an N word phrase match for input containing N instances of the word CARD, i.e., CARD CARD CARD CARD would cause a 4 word phrase match.