(* RG 91's Table of Precedence (docs/research/rules-register.md ยง4). Each branch below is one of the table's 28 entries, checked in the table's own numeric order -- lower wins, and because occasional entries are true exceptions to a later, broader one (RG 91 entry 18's Ember days are an exception carved out of entry 22's Lent ferias; entry 21/26's vigils are an exception carved out of the generic Class2/Class3 sanctoral-feast entries that would otherwise also match), checking in table order and returning on the first match is what makes the exception actually win without a separate exclusion for every later entry it pre-empts. Two kinds of evidence decide an entry: - The temporal cycle's own office (Nativity, a Sunday, a feria, a vigil of the Lord) is identified structurally, from the context's date/season/ weekday and the day's Easter offset -- never from its slug, which is just a label. [origin = Temporal] gates every such entry so a sanctoral candidate that happens to share a date (Immaculate Conception can never coincide with the movable cycle, but nothing stops a future rite bug from producing one) cannot be mistaken for the office itself. - A sanctoral feast's entry (11-13 I class, 14/16/19/20 II class, 23/24 III class) is decided by its [rank] plus, per the brief's structural insight, its {!Celebration.t}.layer: a celebration whose layer is not the universal base is an overlay, hence "proper" or "indult" rather than the universal entry (see precedence_ef.mli). [origin = Sanctoral] gates these for the same reason: temporal-origin celebrations carry the literal layer id "temporal" (rite_ef/temporal_ef.ml's [build]), which is not [universal_layer] either, and would otherwise be misread as "proper" by the layer test alone. Vigils (21, 26) are the one shape neither of those two kinds fully describes on their own: a II/III-class vigil can be temporal-origin (the Ascension Vigil, produced by temporal_ef today) or sanctoral-origin (a saint's vigil, not yet loaded by any task), so its entry cannot be gated on [origin] at all. Nothing in the day's other fields marks "this is a vigil, not an ordinary office of the same rank" either, so this reads it off the temporal cycle's own slug convention (a "-vigil" suffix -- see [named] in temporal_ef.ml) rather than guessing a new one. *) open Colitur_kernel (* Not an RG citation -- RG 91 ranks proper and indult feasts, it does not encode how a computer tells them apart. See precedence_ef.mli. *) let universal_layer = "ef-universal" let indult_prefix = "indult:" let unclassified = max_int let is_indult layer = String.starts_with ~prefix:indult_prefix layer let is_universal layer = String.equal layer universal_layer (* Ember days are identified by the temporal cycle's own slug convention (rite_ef/temporal_ef.ml's [ember]: "ef--ember-"), not re-derived here: the September anchor in particular is one of the more contested dates in the 1962 calendar (temporal_ef.ml's own comment on [third_sunday_of_september]), and re-deriving it a second time would only create a second place for that same uncertainty to drift. Only the Advent, Lent and September sets are listed: RG 91 entry 18 names exactly those three; the Whitsun (Pentecost) set is I class and falls inside the Pentecost octave, entry 10, matched below before this is ever reached. *) let is_ember_18 slug = String.starts_with ~prefix:"ef-advent-ember-" slug || String.starts_with ~prefix:"ef-lent-ember-" slug || String.starts_with ~prefix:"ef-september-ember-" slug let band (ctx : Vocab_ef.season Precedence.context) (c : Vocab_ef.rank Precedence.candidate) : int = let cel = c.Precedence.cel in let rank = cel.Celebration.rank in let subject = cel.Celebration.subject in let layer = cel.Celebration.layer in let slug = Slug.to_string cel.Celebration.slug in let is_temporal = c.Precedence.origin = Precedence.Temporal in let is_vigil = String.ends_with ~suffix:"-vigil" slug in let date = ctx.Precedence.date in let season = ctx.Precedence.season in let weekday = ctx.Precedence.weekday in let is_sunday = weekday = Date.Sun in let m = Date.month date and d = Date.day date in (* Easter offset, the same convention as temporal_ef.ml's [days_between easter d]: 0 is Easter itself, negative before, positive after. *) let off = Date.to_rata date - Date.to_rata (Computus.gregorian_easter (Date.year date)) in let open Vocab_ef in (* 1: Nativity, Easter Sunday, Pentecost Sunday (I class w/ octave). *) if is_temporal && rank = Class1 && ((m = 12 && d = 25) || off = 0 || off = 49) then 1 (* 2: Sacred Triduum (Thu-Sat of Holy Week). *) else if is_temporal && rank = Class1 && off >= -3 && off <= -1 then 2 (* 3: Epiphany, Ascension, Holy Trinity, Corpus Christi, Sacred Heart, Christ the King. *) else if is_temporal && rank = Class1 && ((m = 1 && d = 6) (* Epiphany *) || off = 39 (* Ascension *) || off = 56 (* Trinity *) || off = 60 (* Corpus Christi *) || off = 68 (* Sacred Heart *) || Date.compare date (Temporal_ef.christ_the_king (Date.year date)) = 0) then 3 (* 4: Immaculate Conception, Assumption BVM. *) else if (not is_temporal) && rank = Class1 && ((m = 12 && d = 8) || (m = 8 && d = 15)) then 4 (* 5: Vigil & Octave day of the Nativity. *) else if is_temporal && rank = Class1 && ((m = 12 && d = 24) || (m = 1 && d = 1)) then 5 (* 6: Sundays of Advent, Lent, Passiontide, and Low Sunday. *) else if is_temporal && rank = Class1 && is_sunday && (season = Advent || season = Lent || season = Passiontide || off = 7) then 6 (* 7: I-class ferias not above -- Ash Wednesday; Mon/Tue/Wed of Holy Week. Thu-Sat of Holy Week are the Triduum, entry 2 above, not this entry. *) else if is_temporal && rank = Class1 && (off = -46 || (off >= -6 && off <= -4)) then 7 (* 8: All Souls. *) else if (not is_temporal) && rank = Class1 && m = 11 && d = 2 then 8 (* 9: Vigil of Pentecost. *) else if is_temporal && rank = Class1 && off = 48 then 9 (* 10: Days within the Octaves of Easter and Pentecost. *) else if is_temporal && rank = Class1 && ((off >= 1 && off <= 6) || (off >= 50 && off <= 55)) then 10 (* 11: I-class feasts of the universal Church not above. *) else if (not is_temporal) && (not is_vigil) && rank = Class1 && is_universal layer then 11 (* 12: Proper I-class feasts. *) else if (not is_temporal) && (not is_vigil) && rank = Class1 && not (is_indult layer) then 12 (* 13: Indult I-class feasts. By elimination once 11 and 12 have failed: not the universal layer (11), and marked as an indult overlay (12's "not indult" test having just failed). *) else if (not is_temporal) && (not is_vigil) && rank = Class1 then 13 (* 14: Feasts of the Lord, II class. *) else if (not is_temporal) && (not is_vigil) && rank = Class2 && is_universal layer && subject = Subject.Lord then 14 (* 15: Sundays, II class (every Sunday not already named at 6). *) else if is_temporal && rank = Class2 && is_sunday then 15 (* 16: II-class feasts of the universal Church, not of the Lord. *) else if (not is_temporal) && (not is_vigil) && rank = Class2 && is_universal layer then 16 (* 17: Days within the Octave of the Nativity (26-28 Dec are Stephen, John, the Innocents -- sanctoral, not this entry). *) else if is_temporal && rank = Class2 && m = 12 && (d = 29 || d = 30 || d = 31) then 17 (* 18: II-class ferias -- Advent 17-23 Dec; Ember days of Advent, Lent, September. *) else if is_temporal && rank = Class2 && ((season = Advent && m = 12 && d >= 17 && d <= 23) || is_ember_18 slug) then 18 (* 19: Proper II-class feasts. *) else if (not is_temporal) && (not is_vigil) && rank = Class2 && not (is_indult layer) then 19 (* 20: Indult II-class feasts. By elimination, as at 13. *) else if (not is_temporal) && (not is_vigil) && rank = Class2 then 20 (* 21: II-class vigils (Ascension, Assumption, John Baptist, Peter & Paul -- can be temporal- or sanctoral-origin, see the file comment above). *) else if rank = Class2 && is_vigil then 21 (* 22: Ferias of Lent and Passiontide (Thursday after Ash Wednesday to the Saturday before Palm Sunday), except the Ember days (18 above). *) else if is_temporal && rank = Class3 && (season = Lent || season = Passiontide) then 22 (* 23: III-class feasts in particular calendars. Unlike 11/12 and 14/16 above, the universal entry (24) is the HIGHER number here -- RG 91's own table ranks a particular-calendar III-class feast ahead of a universal one, the reverse of the I/II-class ordering. Transcribed as the register states it, not "corrected" into the other classes' pattern. RG 91 has no indult sub-rank at III class, so every non-base layer lands here, not split further. *) else if (not is_temporal) && (not is_vigil) && rank = Class3 && not (is_universal layer) then 23 (* 24: III-class feasts in the universal calendar. *) else if (not is_temporal) && (not is_vigil) && rank = Class3 then 24 (* 25: Ferias of Advent to 16 Dec, except the Ember days (18 above). *) else if is_temporal && rank = Class3 && season = Advent then 25 (* 26: III-class vigils (St Lawrence). *) else if rank = Class3 && is_vigil then 26 (* 27: Office of the BVM on Saturday -- every otherwise-unoccupied IV-class Saturday, per the historical default that fills it; ordinary Mass propers still make Rogation Mon/Tue/Wed proper without changing the Office (RG 88, see temporal_ef.ml's [temporal]), so those never carry this entry unless they happen to fall on the Saturday itself. Excludes vigils for the same reason 11-13/14/16/19/20/23/24 do: RG 91 has no IV-class vigil at all (its own vigil list, register lines 381-384, stops at III class), so one would be an anomaly, not this entry. *) else if is_temporal && (not is_vigil) && rank = Class4 && weekday = Date.Sat then 27 (* 28: IV-class ferias -- the unqualified catch-all (temporal_ef.ml's own comment on [ferial_rank] cites the same primary text, "Feriae IV classis"). Excludes vigils for the same reason as 27 above: a IV-class "feria" that is also a vigil is not a feria RG 91 describes. *) else if (not is_vigil) && rank = Class4 then 28 else unclassified