Booking lists are linked lists of rule bookings.
+ +- §1. Introduction
- §4. Creation
- §5. Addition
- §6. Removal
- §7. Scanning lists for their contents
- §8. Compilation of rule definitions for rulebook
+ +
§1. Introduction. Bookings are intended to be bound together in linked lists, each of which +represents the interior pages of a single rulebook. +
+ ++typedef struct booking_list { + struct booking *list_head; the dummy entry at the front + CLASS_DEFINITION +} booking_list; ++
- The structure booking_list is private to this section.
§2. There are only three operations on lists: creation, addition of a booking, +and removal of a booking. The following invariants are preserved: +
+ +- (a) The list head is a dummy, i.e., meaningless, booking which has never been +the subject of any addition operation and has never moved. +
- (b) The only FIRST_PLACEMENT entries in the list immediately follow the list +head. Those which were added explicitly as first-placed are in reverse order +of addition to the list. +
- (c) The only LAST_PLACEMENT entries in the list are at the end. Those which +were added explicitly as last-placed are in order of addition to the list. +
- (d) If R and S are middle-placed rules which were placed in the list within +the same range (say, both anywhere, or both "after T" or "before U") and R +precedes S, then either R is more specific than S, or they are equally specific +and R was added to the list before S. +
- (e) The list never contains duplicates, that is, never contains two bookings +whose rules are equal, in the sense of Rules::eq. +
§3. These macros are useful for iterating through the contents in sequence; +note that br is never equal to the dummy head, and that pr is never NULL — +though it is initially equal to the dummy, which of course is the point of +having the dummy. +
+ +define LOOP_OVER_BOOKINGS(br, L) + for (booking *br=(L)?(L->list_head->next_booking):NULL; br; br=br->next_booking) +define LOOP_OVER_BOOKINGS_WITH_PREV(br, pr, L) + for (booking *br = L->list_head->next_booking, *pr = L->list_head; br; + pr = br, br = br->next_booking) ++
+void BookingLists::log(booking_list *L) { + if (L == NULL) { LOG("<null-booked-rule-list>\n"); return; } + int t = BookingLists::length(L); + if (t == 0) { LOG("<empty-booked-rule-list>\n"); return; } + int s = 1; + LOOP_OVER_BOOKINGS(br, L) + LOG(" %d/%d. $b\n", s++, t, br); +} ++
§4. Creation. A new list is a dummy header (see (a) above): +
+ ++booking_list *BookingLists::new(void) { + booking_list *L = CREATE(booking_list); + L->list_head = RuleBookings::new(NULL); + return L; +} ++
§5. Addition. The following is called when a booking br for the rule R needs to be placed +into list L, which is the contents of a rulebook we will call B. This happens, +for example, in response to an an assertion like "R is listed after S in B"; +in that cast the side would be AFTER_SIDE, and the ref_rule would be S. +
+ +It is also possible to specify a placing. For example, for a rule described +as "First every turn rule: ...", this would be added to the every turn rulebook +with placing FIRST_PLACEMENT. There are three possible placings: see booking +for what they are. +
+ +And since there are four possible sides, the following function effectively has +12 different modes to get right. +
+ +Each "addition" leaves the list either the same size or longer by 1. +
+ +define BEFORE_SIDE -1 before the reference rule +define IN_SIDE 0 if no mention is made of where to put the new booking +define AFTER_SIDE 1 after the reference rule +define INSTEAD_SIDE 2 in place of the reference rule ++
+void BookingLists::add(booking_list *L, booking *new_br, + int placing, int side, rule *ref_rule) { + Make some sanity checks on the addition instructions5.1; + Handle the case where the new rule is already in the list5.2; + Handle all placements made with the INSTEAD side5.3; + Handle all placements made with the FIRST placement5.4; + Handle all placements made with the LAST placement5.5; + Handle what's left: MIDDLE placements on the IN, BEFORE or AFTER sides5.6; +} ++
§5.1. Make some sanity checks on the addition instructions5.1 = +
+ ++ if ((side != IN_SIDE) && (ref_rule == NULL)) + internal_error("tried to add before or after or instead of non-rule"); + if ((side == IN_SIDE) && (ref_rule != NULL)) + internal_error("tried to add in middle but with ref rule"); + if ((side != IN_SIDE) && (placing != MIDDLE_PLACEMENT)) + internal_error("tried to add before or after but with non-middle placement"); + if (L == NULL) + internal_error("tried to add rule to null list"); + switch(placing) { + case MIDDLE_PLACEMENT: break; + case FIRST_PLACEMENT: LOGIF(RULE_ATTACHMENTS, "Placed first\n"); break; + case LAST_PLACEMENT: LOGIF(RULE_ATTACHMENTS, "Placed last\n"); break; + default: internal_error("invalid placing of rule"); + } ++
- This code is used in §5.
§5.2. If R is already in B, the assertion may still have an effect. +
+ +If our instructions specify no particular position for R to take, we return +because nothing need be done: the rule's there already, so be happy. Otherwise, +we remove R's existing booking in order that it can be rebooked in a new position. +
+ +This is a change in semantics from the original Inform 7 design for rulebooks, +under which a rule could be booked multiple times in the same rulebook — which +was then called "duplication". Following debate on the Usenet newsgroup +rec.arts.int-fiction in February and March 2009, it was decided to abolish +duplication in favour of the clean principle that a rule can only be in a +single rulebook once. This makes it easier to place rules with tricky preambles, +though there were arguments on both sides. +
+ +Handle the case where the new rule is already in the list5.2 = +
+ ++ LOOP_OVER_BOOKINGS_WITH_PREV(pos, prev, L) + if (Rules::eq(RuleBookings::get_rule(pos), RuleBookings::get_rule(new_br))) { + if ((side == IN_SIDE) && (placing == MIDDLE_PLACEMENT)) return; + prev->next_booking = pos->next_booking; + pos->next_booking = NULL; + LOGIF(RULE_ATTACHMENTS, "Removing previous entry from rulebook\n"); + break; rule can only appear once, so no need to keep checking + } ++
- This code is used in §5.
§5.3. Handle all placements made with the INSTEAD side5.3 = +
+ ++ if (side == INSTEAD_SIDE) { + LOOP_OVER_BOOKINGS_WITH_PREV(pos, prev, L) + if (Rules::eq(RuleBookings::get_rule(pos), ref_rule)) { + new_br->placement = pos->placement; replace with same placement + new_br->next_booking = pos->next_booking; + prev->next_booking = new_br; + } + return; + } ++
- This code is used in §5.
§5.4. If we insert a rule as first-placed rule when there already is a first-placed +rule, the new one displaces it to go first, but both continue to be labelled as +having FIRST_PLACEMENT, so that subsequent rule insertions of middle-placed rules +will still go after both of them. +
+ +Handle all placements made with the FIRST placement5.4 = +
+ ++ if (placing == FIRST_PLACEMENT) { first in valid interval (must be whole list) + booking *previously_first = L->list_head->next_booking; + L->list_head->next_booking = new_br; + new_br->next_booking = previously_first; pushes any existing first rule forward + new_br->placement = placing; + return; + } ++
- This code is used in §5.
§5.5. Symmetrically, a second last-placed rule is inserted after any existing one, but +both are labelled as having LAST_PLACEMENT. +
+ +Handle all placements made with the LAST placement5.5 = +
+ ++ if (placing == LAST_PLACEMENT) { last in valid interval (must be whole list) + booking *prev = L->list_head; + while (prev->next_booking != NULL) prev = prev->next_booking; + prev->next_booking = new_br; pushes any existing last rule backward + new_br->next_booking = NULL; + new_br->placement = placing; + return; + } ++
- This code is used in §5.
§5.6. Handle what's left: MIDDLE placements on the IN, BEFORE or AFTER sides5.6 = +
+ ++ booking *start_rule = L->list_head; valid interval begins after this rule + booking *end_rule = NULL; valid interval ends before this rule, or runs to end if NULL + Adjust the valid interval to take care of BEFORE and AFTER side requirements5.6.1; + Check that the valid interval is indeed as advertised5.6.2; + booking *insert_after = start_rule; insertion point is after this + Find insertion point, keeping the valid interval in specificity order5.6.3; + booking *subseq = insert_after->next_booking; + insert_after->next_booking = new_br; + new_br->next_booking = subseq; + Set the placement for the new rule booking5.6.4; ++
- This code is used in §5.
§5.6.1. Adjust the valid interval to take care of BEFORE and AFTER side requirements5.6.1 = +
+ ++ switch(side) { + case BEFORE_SIDE: + LOOP_OVER_BOOKINGS(pos, L) + if (Rules::eq(RuleBookings::get_rule(pos), ref_rule)) + end_rule = pos; insert before: so valid interval ends here + if (end_rule == NULL) internal_error("can't find end rule"); + break; + case AFTER_SIDE: + LOOP_OVER_BOOKINGS(pos, L) + if (Rules::eq(RuleBookings::get_rule(pos), ref_rule)) + start_rule = pos; insert after: so valid interval begins here + if (start_rule == L->list_head) internal_error("can't find start rule"); + break; + } ++
- This code is used in §5.6.
§5.6.2. Check that the valid interval is indeed as advertised5.6.2 = +
+ ++ int i = 0, t = 2; + if (end_rule == NULL) t = 1; + booking *pos; + for (pos = L->list_head; pos; pos = pos->next_booking) { + if ((pos == start_rule) && (i == 0)) i = 1; + if ((pos == end_rule) && (i == 1)) i = 2; + } + if (i != t) internal_error("valid rule interval isn't"); ++
- This code is used in §5.6.
§5.6.3. Find insertion point, keeping the valid interval in specificity order5.6.3 = +
+ ++ int log = FALSE; if (Log::aspect_switched_on(SPECIFICITIES_DA)) log = TRUE; + + move forward to final valid first rule (if any exist) + while ((insert_after->next_booking != end_rule) + && (insert_after->next_booking->placement == FIRST_PLACEMENT)) + insert_after = insert_after->next_booking; + + move forward past other middle rules if they are not less specific + while ((insert_after->next_booking != end_rule) stop before \(p\) leaves valid range + && (insert_after->next_booking->placement != LAST_PLACEMENT) or reaches a last rule + && (RuleBookings::cmp(insert_after->next_booking, new_br, + log) >= 0)) or a rule less specific than the new one + insert_after = insert_after->next_booking; ++
- This code is used in §5.6.
§5.6.4. Since this part of the algorithm is used only when we've requested MIDDLE +placement, it might seem that new_br->placement should always be set to +that. This does indeed mostly happen, but not always. To preserve rulebook +invariants (b) and (c), we need to force anything added after a LAST rule +to be LAST as well, and similarly for FIRSTs. (This will only happen in +cases where the source text called for placements AFTER a LAST rule, or +BEFORE a FIRST one.) +
+ +Set the placement for the new rule booking5.6.4 = +
+ ++ new_br->placement = MIDDLE_PLACEMENT; + if ((insert_after != L->list_head) && + (insert_after->placement == LAST_PLACEMENT)) + new_br->placement = + LAST_PLACEMENT; happens if valid interval is after a last rule + + if ((subseq) && + (subseq->placement == FIRST_PLACEMENT)) + new_br->placement = + FIRST_PLACEMENT; happens if valid interval is before a first rule ++
- This code is used in §5.6.
§6. Removal. This is much simpler, since it doesn't disturb the ordering: +
+ ++void BookingLists::remove(booking_list *L, rule *R) { + LOOP_OVER_BOOKINGS_WITH_PREV(br, pr, L) + if (Rules::eq(RuleBookings::get_rule(br), R)) { + pr->next_booking = br->next_booking; + return; + } +} ++
§7. Scanning lists for their contents.
+ ++int BookingLists::length(booking_list *L) { + int n = 0; + LOOP_OVER_BOOKINGS(br, L) n++; + return n; +} + +int BookingLists::is_contextually_empty(booking_list *L, rule_context rc) { + LOOP_OVER_BOOKINGS(br, L) { + phrase *ph = Rules::get_defn_as_phrase(RuleBookings::get_rule(br)); + if (Rulebooks::phrase_fits_rule_context(ph, rc)) return FALSE; + } + return TRUE; +} + +booking *BookingLists::first(booking_list *L) { + LOOP_OVER_BOOKINGS(br, L) return br; + return NULL; +} + +int BookingLists::is_empty_of_i7_rules(booking_list *L) { + LOOP_OVER_BOOKINGS(br, L) + if (Rules::get_defn_as_phrase(RuleBookings::get_rule(br))) + return FALSE; + return TRUE; +} + +int BookingLists::contains(booking_list *L, rule *to_find) { + LOOP_OVER_BOOKINGS(br, L) + if (Rules::eq(RuleBookings::get_rule(br), to_find)) + return TRUE; + return FALSE; +} + +int BookingLists::contains_ph(booking_list *L, phrase *ph_to_find) { + LOOP_OVER_BOOKINGS(br, L) + if (Rules::get_defn_as_phrase(RuleBookings::get_rule(br)) == ph_to_find) + return TRUE; + return FALSE; +} ++
§8. Compilation of rule definitions for rulebook. There's no real need to do so, but we compile rule definitions in rulebook +order to make the compiled code more legible, and for the same reason we add +plenty of commentary. +
+ ++void BookingLists::compile(booking_list *L, int *i, int max_i) { + int t = BookingLists::length(L); + int s = 0; + LOOP_OVER_BOOKINGS(br, L) { + s++; + RTRules::compile_comment(RuleBookings::get_rule(br), s, t); + if (br->next_booking) { + TEMPORARY_TEXT(C) + if (br->placement != br->next_booking->placement) { + WRITE_TO(C, "--- now the "); + switch(br->next_booking->placement) { + case FIRST_PLACEMENT: WRITE_TO(C, "first-placed rules"); break; + case MIDDLE_PLACEMENT: WRITE_TO(C, "mid-placed rules"); break; + case LAST_PLACEMENT: WRITE_TO(C, "last-placed rules"); break; + } + WRITE_TO(C, " ---"); + Produce::comment(Emit::tree(), C); + } else { + RuleBookings::comment(C, br); + if (Str::len(C) > 0) Produce::comment(Emit::tree(), C); + } + DISCARD_TEXT(C) + } + } + if (t > 0) CompiledText::divider_comment(); +} ++ + +