aboutsummaryrefslogtreecommitdiff
path: root/lib/rites/rite_ef
diff options
context:
space:
mode:
Diffstat (limited to 'lib/rites/rite_ef')
-rw-r--r--lib/rites/rite_ef/precedence_ef.ml173
-rw-r--r--lib/rites/rite_ef/precedence_ef.mli35
2 files changed, 208 insertions, 0 deletions
diff --git a/lib/rites/rite_ef/precedence_ef.ml b/lib/rites/rite_ef/precedence_ef.ml
new file mode 100644
index 0000000..50da4a7
--- /dev/null
+++ b/lib/rites/rite_ef/precedence_ef.ml
@@ -0,0 +1,173 @@
+(* 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-<set>-ember-<day>"), 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
diff --git a/lib/rites/rite_ef/precedence_ef.mli b/lib/rites/rite_ef/precedence_ef.mli
new file mode 100644
index 0000000..e8d4a11
--- /dev/null
+++ b/lib/rites/rite_ef/precedence_ef.mli
@@ -0,0 +1,35 @@
+(** RG 91's Table of Precedence for the EF (1962) rite: ranks any candidate
+ for a given day by its RG 91 entry number. See
+ docs/research/rules-register.md §4, whose 28-entry transcription this
+ module follows line by line. *)
+
+open Colitur_kernel
+
+(** The universal (base) sanctoral layer's {!Celebration.t}.layer id. A
+ Sanctoral-origin candidate whose layer is anything else is an overlay:
+ "proper" (RG 91 entries 12, 19, 23) unless its layer id also carries
+ {!indult_prefix} ("indult", entries 13, 20). This id and the prefix are
+ colitur's own data-modelling convention, not an RG citation -- RG 91
+ prescribes the ranking, not a machine encoding for it. Whichever task
+ loads the real EF sanctoral base layer and its overlays must either
+ reuse these two constants or this classifier will misfile them. *)
+val universal_layer : string
+
+(** See {!universal_layer}. *)
+val indult_prefix : string
+
+(** Returned for a candidate shape RG 91's 28-entry table has no row for --
+ e.g. a [Class1] vigil that is not the Nativity or Pentecost (entries 5,
+ 9 are the only I-class vigils the table names), or a [Class4] candidate
+ also marked as a vigil. Deliberately outside 1..28 and larger than any
+ real entry, so an unclassified candidate can never win an occurrence
+ contest by accident; a caller that sees it back knows the shape needs a
+ new rule, not a silently wrong one. *)
+val unclassified : int
+
+(** [band ctx c]: RG 91's Table of Precedence. Returns the table's own entry
+ number -- I class 1-13, II class 14-21, III class 22-26, IV class 27-28;
+ lower wins (see {!Precedence.rules.band}). Total over every candidate
+ {!Precedence.resolve} or {!Calendar} can construct, including shapes the
+ 1962 table itself does not describe (see {!unclassified}). *)
+val band : Vocab_ef.season Precedence.context -> Vocab_ef.rank Precedence.candidate -> int