1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
|
(* 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, All Souls)
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. All Souls (entry 8, the
one non-temporal-origin member of this group) additionally reads the
context's weekday for its own register-stated exception -- see entry 8
below.
- A sanctoral feast's entry is decided by its [rank], and -- except at
entry 14 (see its own comment below, where the register draws no such
line) -- per the brief's structural insight, also by 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 (11-13 I class; 16/19/20 II class; 23/24 III class; 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
(* Not an RG citation either -- see [universal_layer] above. Nothing in
{!Celebration.t} otherwise marks "this is a vigil, not an ordinary office
of the same rank" (see the file's top comment), so entries 21/26 read it
off the temporal cycle's own slug suffix (rite_ef/temporal_ef.ml's
[named], e.g. "ef-ascension-vigil"). Exposed so a future task naming a
sanctoral vigil (Task 10: Assumption, John Baptist, Peter & Paul,
Lawrence -- only Ascension exists today) uses the same suffix; a
differently-named vigil would band 16/24 instead of 21/26, silently. *)
let vigil_suffix = "-vigil"
let is_vigil slug = String.ends_with ~suffix:vigil_suffix slug
(* Not an RG citation -- see [universal_layer]. Entry 18's 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.
Exposed for the same reason as [vigil_suffix]: a rename of temporal_ef's
format has somewhere to be caught other than a silently-wrong entry 18. *)
let ember_prefixes = [ "ef-advent-ember-"; "ef-lent-ember-"; "ef-september-ember-" ]
let is_ember_18 slug = List.exists (fun prefix -> String.starts_with ~prefix slug) ember_prefixes
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 = is_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
(* Named so entry 8's Sunday exception below can read "one worse than the
Sunday it must yield to" rather than a bare integer that happens to
equal entry 15's own value; entry 15's own branch returns this same
binding, not a second literal, so the two can never drift apart. *)
let entry_15_band = 15 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 -- register line 334's own text carries a qualifier this
transcription must honour: "yields to an occurring Sunday". 2 November
is always Time_after_pentecost (well clear of Advent/Lent/Passiontide
and of every other entry's own Easter-relative or fixed date), so a
Sunday landing on it is always an ordinary entry-15 II-class Sunday --
the one and only rival this exception ever has to lose to. On such a
Sunday this returns [entry_15_band + 1]: strictly worse than 15 (an
exact tie would fall to Precedence.resolve's slug tie-break, which
for "ef-all-souls" against a "ef-time-after-pentecost-sunday-*" slug
would make All Souls WIN -- the precise bug this guards against), but
otherwise not a citation to any other RG 91 row -- nothing else can
ever occur on 2 November to be confused with it. Entry 8's own [rank]
is untouched by this, so Task 8's disposition (RG 95: only I-class
feasts transfer) still sees the true I-class candidate it needs to
move to 3 November. *)
else if (not is_temporal) && rank = Class1 && m = 11 && d = 2 then
if is_sunday then entry_15_band + 1 else 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 -- register line 341, deliberately
UNQUALIFIED (contrast entry 16 at line 342, which explicitly says "not
of the Lord"; RG 37c, register line 393, speaks of "II-class feasts of
the Lord" replacing an occurring II-class Sunday with no universal
qualifier either). No layer test here, unlike 11/12/13 and 16/19/20:
the register does not split this entry into universal/proper/indult,
so a proper or indult feast of the Lord still bands 14, not 19/20. *)
else if (not is_temporal) && (not is_vigil) && rank = Class2 && 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 entry_15_band
(* 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
|