To enforce the domain of properties: for instance, that a door can be open or closed but that an animal cannot, or that a person can have a carrying capacity but that a door cannot.


§1. Properties are pieces of data attached to their "owners", but they also have a common identity wherever they turn up. For example, the property "carrying capacity" is owned by any person and also by any container, but it has a common meaning in each case, and in each case it's a number.

Only values can own properties at run-time, but kinds can own properties during compilation. Thus the Standard Rules declare that the kinds "person" and "container" have a number called "carrying capacity", and we record that as a single property which has two owners, both kinds. In the final story file as compiled, each individual person and container then inherits the property.

And so each inference subject has a list of properties it can provide, and each property a list of subjects it can be provided by. These are each lists of property_permission objects.

typedef struct property_permission {
    struct inference_subject *property_owner;  to whom permission is granted
    struct property *property_granted;  which property is permitted

    struct parse_node *where_granted;  sentence granting the permission

    struct general_pointer pp_storage_data;  how we'll compile this at run-time
    void *feature_pp[MAX_COMPILER_FEATURES];  storage for features to attach, if they want to

    struct property_permission_compilation_data compilation_data;
    CLASS_DEFINITION
} property_permission;

§2. Seeking permission. Note that an either/or property and its antonym (say, "open" and "closed") are equivalent here: permission for one is always permission for the other.

If these were long lists, or searched often, it would be faster to move each found permission to the front, thus tending to move frequently-sought properties to the start. Profiling shows that this would save no significant time, whereas the unpredictable order might make testing Inform more annoying.

define LOOP_OVER_PERMISSIONS_FOR_PROPERTY(pp, prn)
    LOOP_OVER_LINKED_LIST(pp, property_permission, Properties::get_permissions(prn))
define LOOP_OVER_PERMISSIONS_FOR_INFS(pp, infs)
    LOOP_OVER_LINKED_LIST(pp, property_permission, InferenceSubjects::get_permissions(infs))
property_permission *PropertyPermissions::find(inference_subject *infs,
    property *prn, int allow_inheritance) {
    property *prnbar = NULL;
    if (Properties::is_either_or(prn)) prnbar = EitherOrProperties::get_negation(prn);

    if (prn)
        while (infs) {
            property_permission *pp;
            LOOP_OVER_PERMISSIONS_FOR_INFS(pp, infs)
                if ((pp->property_granted == prn) || (pp->property_granted == prnbar))
                    return pp;
            infs = (allow_inheritance)?
                    (InferenceSubjects::narrowest_broader_subject(infs)):NULL;
        }

    return NULL;
}

§3. Granting permission. This does nothing if permission already exists, simply returning the existing permission structure; but note the use of allow_inheritance. If this is set to FALSE, and we call for the "carrying capacity" property of the player (say), then we may create a new permission even though the player's kind ("person") already has one. This is intentional.1

property_permission *PropertyPermissions::grant(inference_subject *infs, property *prn,
    int allow_inheritance) {
    property_permission *new_pp = PropertyPermissions::find(infs, prn, allow_inheritance);
    if (new_pp == NULL) {
        LOGIF(PROPERTY_PROVISION, "Allowing $j to provide $Y\n", infs, prn);
        Create the new permission structure3.1;
        Add the new permission to the owner's list3.2;
        Add the new permission to the property's list3.3;
        Notify plugins that a new permission has been issued3.4;
    }
    return new_pp;
}

§3.1. Create the new permission structure3.1 =

    new_pp = CREATE(property_permission);
    new_pp->property_owner = infs;
    new_pp->property_granted = prn;
    new_pp->where_granted = current_sentence;
    new_pp->compilation_data = RTPropertyPermissions::new_compilation_data(new_pp);
    InferenceSubjects::new_permission_granted(infs, new_pp);

§3.2. Add the new permission to the owner's list3.2 =

    linked_list *L = InferenceSubjects::get_permissions(infs);
    ADD_TO_LINKED_LIST(new_pp, property_permission, L);

§3.3. Add the new permission to the property's list3.3 =

    linked_list *L = Properties::get_permissions(prn);
    ADD_TO_LINKED_LIST(new_pp, property_permission, L);

§3.4. Complicating matters, features have the ability to attach data of their own to a permission. For instance, the "parsing" feature attaches the idea of a property being visible — we might say that every thing has an interior colour, but that it is invisible in the case of a dog and visible in the case of a broken jar.

Notify plugins that a new permission has been issued3.4 =

    for (int i=0; i<MAX_COMPILER_FEATURES; i++) new_pp->feature_pp[i] = NULL;
    PluginCalls::new_permission_notify(new_pp);

§4. These two macros provide access to feature-specific permission data:

define PP_FEATURE_DATA(id, pp)
    ((id##_pp_data *) pp->feature_pp[id##_feature->allocation_id])
define CREATE_PP_FEATURE_DATA(id, pp, creator)
    (pp)->feature_pp[id##_feature->allocation_id] = (void *) (creator(pp));

§5. Boring access functions.

property *PropertyPermissions::get_property(property_permission *pp) {
    return pp->property_granted;
}

inference_subject *PropertyPermissions::get_subject(property_permission *pp) {
    return pp->property_owner;
}

general_pointer PropertyPermissions::get_storage_data(property_permission *pp) {
    return pp->pp_storage_data;
}

parse_node *PropertyPermissions::where_granted(property_permission *pp) {
    return pp->where_granted;
}