Bookings are assignments of rules to rulebooks.


§1. Introduction. Rules are only really of use in rulebooks, but in fact they can belong to any number of rulebooks. Each placement of a rule in a rulebook is called a "booking", and results in a booking object. These are gathered into linked lists as booking_list objects: see Booking Lists.

We can think of rule bookings as being looseleaf pages, of which we have an unlimited supply: any rule can be written on them. They can be bound into any rulebook at any specified position, but each rulebook divides into three non-overlapping sections: the first rules, the middle rules, the last rules. And each booking records which of these sections it belongs to.

enum MIDDLE_PLACEMENT from 0  most bookings are in this middle section
enum FIRST_PLACEMENT
enum LAST_PLACEMENT
typedef struct booking {
    struct rule *rule_being_booked;  what appears on this page
    int placement;  one of the *_PLACEMENT values above
    int place_automatically;  should this be inserted automatically?

    struct booking_commentary commentary;  used only for indexing and code comments

    struct booking *next_booking;  in its booking list
    CLASS_DEFINITION
} booking;

§2. Here's a rather arcane notation used in the debugging log:

void RuleBookings::log(booking *br) {
    if (br == NULL) { LOG("BR:<null-booked-rule>"); return; }
    LOG("BR%d", br->allocation_id);
    switch (br->placement) {
        case MIDDLE_PLACEMENT: LOG("m"); break;
        case FIRST_PLACEMENT: LOG("f"); break;
        case LAST_PLACEMENT: LOG("l"); break;
        default: LOG("?"); break;
    }
    Rules::log(br->rule_being_booked);
}

§3. Creation.

booking *RuleBookings::new(rule *R) {
    booking *br = CREATE(booking);
    br->next_booking = NULL;
    br->rule_being_booked = R;
    br->placement = MIDDLE_PLACEMENT;
    br->place_automatically = FALSE;
    br->commentary = RuleBookings::new_commentary();
    return br;
}

§4. Access.

rule *RuleBookings::get_rule(booking *br) {
    if (br == NULL) return NULL;
    return br->rule_being_booked;
}

§5. Placement. When created, a booking is like a piece of looseleaf paper which has not yet been put into a binder (i.e., into the booking_list of a rulebook); the process of putting it into such a list is called "placement".

Most rules declare a single rulebook to belong to in their definitions:

Before eating something: ...

This nameless rule is to go into the "before eating" rulebook. When Inform reads this, it creates a booking, but does not immediately place this into the rulebook; instead, the booking is marked for automatic placement later on.

void RuleBookings::request_automatic_placement(booking *br) {
    br->place_automatically = TRUE;
}

§6. And now it's later on:

void RuleBookings::make_automatic_placements(void) {
    booking *br;
    LOOP_OVER(br, booking)
        if (br->place_automatically) {
            imperative_defn *id = Rules::get_imperative_definition(br->rule_being_booked);
            if (id) {
                current_sentence = id->at;
                id_body *idb = id->body_of_defn;
                rulebook *original_owner = RuleFamily::get_rulebook(idb->head_of_defn);
                int placement = RuleFamily::get_rulebook_placement(idb->head_of_defn);
                rulebook *owner = original_owner;
                PluginCalls::place_rule(br->rule_being_booked, original_owner, &owner);
                if (owner != original_owner) {
                    RuleFamily::set_rulebook(idb->head_of_defn, owner);
                    LOGIF(RULE_ATTACHMENTS, "Rerouting $b: $K --> $K\n",
                        br, original_owner, owner);
                }
                Rulebooks::attach_rule(owner, br, placement, 0, NULL);
                Rules::set_kind_from(br->rule_being_booked, owner);
            } else {
                internal_error("Inter-defined rules cannot be automatically placed");
            }
        }
}

§7. Specificity of bookings. This strcmp-like function is intended to be used in sorting algorithms, and returns 1 if br1 is more specific than br2, -1 if br2 is more specific than br1, or 0 if they are equally good.

int RuleBookings::cmp(booking *br1, booking *br2, int log_this) {
    if ((br1 == NULL) || (br2 == NULL)) internal_error("compared null specificity");
    if (log_this) LOG("Comparing specificity of rules:\n(1) $b\n(2) $b\n", br1, br2);
    return Rules::cmp(br1->rule_being_booked, br2->rule_being_booked, log_this);
}

§8. Commentary. The sorting algorithm for rulebooks is very important, and Inform authors sometimes need to know why rulebooks come out in a particular order. So each booking includes a booking_commentary which explains how it came to end up where it did. This is used only for code comments and the index.

typedef struct booking_commentary {
    int next_rule_specificity;  1 for more specific than following, 0 equal, -1 less
    struct text_stream *tooltip_text;  description of reason
    struct text_stream *law_applied;  name of Law used to sort
} booking_commentary;

booking_commentary RuleBookings::new_commentary(void) {
    booking_commentary bc;
    bc.next_rule_specificity = 0;
    bc.tooltip_text = NULL;
    bc.law_applied = NULL;
    return bc;
}

void RuleBookings::comment(OUTPUT_STREAM, booking *br) {
    text_stream *law = br->commentary.law_applied;
    switch(br->commentary.next_rule_specificity) {
        case -1: WRITE("  <<< %S <<<", law); break;
        case 0: WRITE("  === equally specific with ==="); break;
        case 1: WRITE("  >>> %S >>>", law); break;
    }
}

void RuleBookings::list_judge_ordering(booking_list *L) {
    LOOP_OVER_BOOKINGS(br, L)
        if (br->next_booking) {
            if (br->placement != br->next_booking->placement)
                Calculate specificities when placements differ8.1
            else
                Calculate specificities when placements are the same8.2;
        } else {
            br->commentary.next_rule_specificity = 0;
            br->commentary.tooltip_text = NULL;
        }
}

§8.1. Calculate specificities when placements differ8.1 =

    br->commentary.next_rule_specificity = 1;
    switch(br->placement) {
        case FIRST_PLACEMENT:
            switch(br->next_booking->placement) {
                case MIDDLE_PLACEMENT:
                    br->commentary.tooltip_text =
                    I"the rule above was listed as 'first' so precedes this one, which wasn't";
                    break;
                case LAST_PLACEMENT:
                    br->commentary.tooltip_text =
                    I"the rule above was listed as 'first' so precedes this one, listed as 'last'";
                    break;
                default:
                    BookingLists::log(L);
                    internal_error("booking list invariant broken");
                    break;
            }
            break;
        case MIDDLE_PLACEMENT:
            switch(br->next_booking->placement) {
                case LAST_PLACEMENT:
                    br->commentary.tooltip_text =
                    I"the rule below was listed as 'last' so comes after this one, which wasn't";
                    break;
                default:
                    BookingLists::log(L);
                    internal_error("booking list invariant broken");
                    break;
            }
            break;
        default:
            BookingLists::log(L);
            internal_error("booking list invariant broken");
            break;
    }

§8.2. Calculate specificities when placements are the same8.2 =

    br->commentary.next_rule_specificity = 0;
    switch(br->placement) {
        case FIRST_PLACEMENT:
            br->commentary.tooltip_text =
            I"these rules were both listed as 'first', so they appear in reverse order of listing";
            break;
        case MIDDLE_PLACEMENT:
            br->commentary.next_rule_specificity =
                RuleBookings::cmp(br, br->next_booking, FALSE);
            if (br->commentary.next_rule_specificity == 0) br->commentary.tooltip_text =
            I"these rules are equally ranked";
            else {
                br->commentary.tooltip_text =
                I"the arrow points from a more specific rule to a more general, as decided by Law";
                br->commentary.law_applied = Specifications::law_applied();
            }
            break;
        case LAST_PLACEMENT:
            br->commentary.tooltip_text =
            I"these rules were both listed as 'last', so they appear in order of listing";
            break;
    }