Man selection core
From LSWiki
Contents |
Files
/lib/rarity.h /lib/selection.h /mod/daemon/selection.c
Examples
/daemon/materials.c, /lib/materials.h /daemon/races.c, /etc/races.h
Introduction
This document describes what you need to do in order to implement a selection parser. There are two reasons you might want to do this. First, you may be a core systems developer looking to implement a new core-level selection parser beyond those for materials, races, items, characters, and rooms (described in 'man selection'). (Those of that list that aren't implemented yet should be left to me, thank you kindly, unless you don't hear from me for many, many moons.) Second, you may be a skilled content developer who feels that implementing a selection parser in one of your projects would be particularly useful for some purpose not covered adequately by any other mechanism.
Either way, this document will tell you exactly what you need to do in order to have your parser work sanely. Referring to the examples mentioned above is likely to be quite edifying, especially with this document to explain why they're doing what they're doing. I'll mention points where you particularly may want to peruse examples.
Core Assumptions
There are several assumptions that this document makes:
1) That you have some pool of things -- anything that can be represented by an LPC variable of any kind -- that you want to select from.
2) That you need to work with this pool in a number of different ways and situations, such that a selection parser would be a more appropriate application than using the rarity mechanics directly or simply employing random_element().
3) That you have, or can obtain, a concept of how rare you want each of these things to be (as defined by /lib/rarity.h).
4) That the selection language defined in selection.h is adequate for the type of thing you want to do with your pool of things.
If any of these assumptions is not the case, then a selection parser is not the solution for your problem. If #4 is not true because you would need additional designators or other language elements added, you can send me mudmail asking for what you need, but I don't recommend trying to add it any other way; I'm very likely to splatter your changes, quite by accident, when I install new versions of selection.h. The Custom designator provides a totally flexible workaround for this issue, of course.
Basic Setup
Selection parsers are normally implemented as daemons, so it is assumed you will be inheriting /std/daemon. In addition, you need to inherit /mod/daemon/selection and include selection.h. /mod/daemon/selection does not define any of the core functions like create(), reset(), and so on, so you don't need to worry about calling ::create() and the like for it.
It may possibly, for some reason, be reasonable to implement a selection parser in some centralized location other than a daemon. However, if you were to implement a selection parser in a cloned object or do something so that a new parser were created every time you wanted to request a selection, I would probably eviscerate you upon discovering this. I don't want that. You don't want that.
You will want a header file that defines the standard selection-type macros for your newly created selection class. /etc/races.h is a lovely example. These macros should look like:
#define Random_Thing(x) \ (Your_Daemon->select_output(x, Select_Output_Single)) #define Random_Things(x, y) \ (Your_Daemon->select_output(x, Select_Output_Multiple, y)) #define Random_Things_Unique(x, y) \ (Your_Daemon->select_output(x, Select_Output_Unique, y)) #define Selected_Things(x) \ (Your_Daemon->select_output(x, Select_Output_All)) #define Selected_Things_Distribution(x) \ (Your_Daemon->select_output(x, Select_Output_Distribution))
This example is appropriate to a lib-level parser. If you are doing this for a project, the appropriate header file is your project header file, and these macros are NOT exempt from normal namespace considerations, and so will probably look much like:
#define Project_Random_Thing(x) \ (Project_Daemon("thing")->select_output(x, Select_Output_Single)) #define Project_Random_Things(x, y) \ (Project_Daemon("thing")->select_output(x, Select_Output_Multiple, y)) #define Project_Random_Things_Unique(x, y) \ (Project_Daemon("thing")->select_output(x, Select_Output_Unique, y)) #define Project_Selected_Things(x) \ (Project_Daemon("thing")->select_output(x, Select_Output_All)) #define Project_Selected_Things_Distribution(x) \ (Project_Daemon("thing")->select_output(x, Select_Output_Distribution))
Implementing Your Hooks
There are three "hooks" the parser requires, and one which is optional. ("Hook" is used here in a slightly different sense than with the general hook mechanism in the lib, since the people messing with things at this level can be expected not to be confused by this.) Your parser must implement the following functions. The static flag is not strictly necessary, but recommended. You must NOT use the private flag for these or any other functions required by the parser, because then the parser cannot access them.
static int select_hook_rarity(mixed array parse, mixed what)
A required hook. 'parse' is the parse environment, as defined in selection.h, and 'what' is the thing that the parser is requesting a rarity for. This function must return the rarity you want associated with the thing given.
static mixed array select_hook_entire_pool(mixed array parse)
A required hook. 'parse', again, is the parse environment. This function must return an array containing your entire pool of things. Order is insignificant.
static mixed array select_hook_closure_args(mixed array parse, mixed what)
A required hook. The arguments are the same as in the rarity hook. This function must return an array of the arguments you want passed to user-defined closures encountered by the parser. Customarily, the options mapping from the parse environment is made the last argument. If you don't know what to do with this hook, you can't go far wrong with this:
return ({ what, parse[Select_Parse_Env_Options] });
static mixed select_hook_convert_output(mixed array parse, mixed what)
An optional hook. The arguments are the same as in the rarity hook. This hooks allows you to postprocess the output which is produced by the selection system. The return value of this hook will replace the item as it would normally be returned. Note that this function must be able to handle the case when nothing could be selected and 'what' is zero.
Implementing Your Designators
I assume that you are familiar with the concept of designators from 'man selection'. There are three designators that the parser provides on its own using the hooks you give: All, Custom, and Rarity. You do not need to, and in fact cannot, provide support for these. Any designators beyond these that you wish your parser to implement, you need to provide support functions for. Feel free at all times to refer to the examples to see how it's done.
A designator support function is defined by its name being the string "select_designator_" + lower_case(replace(" ", "_", Designator_Name)). So, the name of the support function for the Anatomy Component designator is select_designator_anatomy_component().
Say you have a pool of things in which some are sentient and some are not, so you want to support the Sentience designator in your parser. You also happen to have a global array, called 'sentient', that has a list of the items in your pool that are sentient. Your support function might then look like this:
static void select_designator_sentience(mixed array parse) {
mixed arg = select_pop_stack(parse); unless(statusp(arg)) select_error("Non-boolean argument for sentience", arg); foreach(mixed thing : sentient) select_hit(parse, thing);
}
You will see here that we have introduced a few new concepts: the functions select_pop_stack(), select_error(), and select_hit(). These are utility functions provided by /mod/daemon/selection for your use in implementing your parser. Their care and feeding is described in the next section.
Parser Utility Functions
mixed select_pop_stack(mixed array parse)
This function is how you obtain the data that the user has passed as an argument to your designator. You must pop off no more and no less than the number of values your designator takes as arguments; one for Sentience, Property, Material, two for Value, and so on. Doing otherwise will screw up the parsing process and cause errors. You pass as an argument the parse environment your support function is passed.
void select_hit(mixed array parse, mixed what)
This function is how you tell the parser that your designator has found a thing that it applies to. The first argument is the parse environment your support function is passed, and the second is the thing your designator wants to designate.
varargs void select_error(string message, mixed value)
This function aborts execution with an error from the selection parsing process. The first argument is the string message you want displayed. the second, optional, argument is any value particularly relevant to the error, which select_error() will present in as useful a fashion as it can determine as part of the error message.
Implementing Your Options
If you would like to support any parser-level options, you need to define support functions for these as well. The name of the necessary support function for a given option is "select_option_" + option_name. (Note that this means option names cannot have any character that is not valid in a function name.) An option support function is like a designator with one argument, so you need to pop one value off the stack. A support function for an option called "allow_unique" might look like this:
static void select_option_allow_unique(mixed array parse) {
mixed val = select_pop_stack(parse); unless(statusp(val)) select_error("Non-boolean argument for allow_unique", val); parse[Select_Parse_Env_Options]["allow_unique"] = val;
}
There is no specific need to use the options mapping, or to access only the name of your option; you can manipulate global variables or whatever else in your option support function. The options mapping is there for your convenience if it's useful to you.
Conclusion
Implemented your hooks? Implemented the designators you want to support? Implemented any options you want? Then you have a (hopefully) working parser. Test, debug, wash, rinse, repeat.