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
217
218
219
220
221
222
223
224
225
226
|
(** 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
(** Slug suffix marking a celebration as a vigil (RG 91 entries 21, 26),
e.g. "ef-ascension-vigil" -- colitur's own temporal-cycle convention
(rite_ef/temporal_ef.ml's [named]). Also colitur's own convention, not
an RG citation, exposed for the same reason as {!universal_layer}. See
{!vigil_prefix} for the sanctoral data's own, different convention: a
vigil can arrive named either way, and {!band}/{!disposition} must
recognise both. *)
val vigil_suffix : string
(** Slug prefix marking a celebration as a vigil, e.g. "vigil-of-st-lawrence"
-- the sanctoral data's own convention (data/ef/sanctoral.sexp, adopted
verbatim from lectio's naming, per spec §4.4's "slugs are lectionary keys,
not re-derived"). Also colitur's own convention, not an RG citation --
see {!universal_layer}. Task 10 bootstrapped four real sanctoral vigils
named this way (St Lawrence 08-09, Sts Peter & Paul 06-28, the Assumption
08-14, the Nativity of St John the Baptist 06-23; a fifth, Christmas, is
suppressed as a duplicate of the temporal cycle's own "ef-nativity-vigil"
-- see data/ef/adjustments.sexp), none of which end in {!vigil_suffix}:
without this prefix also being checked, {!band} would misfile all four at
16/24 (an ordinary feast of the same rank) instead of RG 91's 21/26, and
RG 33's vigil omission ({!disposition}'s [is_vigil] test, the same
predicate) would never fire for them either -- two rubrics silently
broken for four celebrations, exactly what Task 7's review predicted
when it asked for {!vigil_suffix} to be exposed. *)
val vigil_prefix : string
(** Slug prefixes marking a celebration as one of RG 91 entry 18's three
Ember-day sets (Advent, Lent, September -- the Pentecost/Whitsun set is
I class and matched by entry 10 before this is ever consulted). Also
colitur's own convention mirroring rite_ef/temporal_ef.ml's own "ef-<set>
-ember-<day>" slug format, not re-derived from first principles; exposed
so a rename of that format has somewhere to be caught other than a
silently-wrong entry 18. *)
val ember_prefixes : string list
(** 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}) -- EXCEPT where the table's own
text states an exception: entry 8 (All Souls, register line 334) reads
"yields to an occurring Sunday", so on a Sunday this returns a value that
loses to entry 15 rather than the literal integer 8 (see the comment on
entry 8 in precedence_ef.ml for the exact value and why). 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
(** RG 33's marker: every Sunday slug this rite's temporal cycle produces
(temporal_ef.ml's [named] and [sunday_slug]) contains this substring;
nothing else {!band} classifies does. Also colitur's own convention, not
an RG citation -- see {!universal_layer} -- exposed for the same reason
as {!vigil_suffix}: a rename of temporal_ef's Sunday-slug format has
somewhere to be caught other than a silently-wrong RG 33 disposition. *)
val sunday_marker : string
(** [disposition ~winner ~loser]: RG 92-95, 33, 94 (docs/research/
rules-register.md §4, "Occurrence" and "Vigils"). What becomes of a
losing candidate, decided by the LOSER's own rank and status (RG 95),
except RG 33's vigil omission, which also reads the winner:
- a {!Celebration.status} of [Commemoration_only] is always
[Commemorate] (checked first: it can never win -- see
{!Precedence.resolve} -- and, by that same status's own definition,
already denotes an office with nothing left to translate, so it never
transfers either; not itself a further RG citation beyond RG 93's
general four-mechanism statement above);
- a [Class1] or [Class2] loser whose slug marks it a vigil ({!vigil_suffix})
is [Omit] when the winner is any Sunday ({!sunday_marker}) or itself
[Class1] (RG 33 -- entirely omitted, not merely commemorated);
- any other [Class1] loser that is NOT a Sunday ({!sunday_marker}) is
[Transfer] (RG 95, register lines 323, 363 -- only I-class FEASTS have
the right of translation; RG 91's own table lists Sundays as a
separate row, entry 6, from feasts, entries 11-13, so a Sunday is
never a "feast" in RG 95's sense and does not transfer even when
impeded by a higher I-class day. This is also what moves All Souls,
register line 334, once it loses to an occurring Sunday -- WHERE it
lands is {!Rite.t.transfer_target}'s job, not this function's);
- everything else -- including an impeded I-class Sunday -- is
[Commemorate], carrying its real RG 109 privilege (see {!admit}
below); RG 109(a) (register line 374) lists "of a Sunday" as a
privileged commemoration category precisely because an impeded Sunday
stays put rather than moving to another day.
Total over every winner/loser pair {!Precedence.resolve} or {!Calendar}
can construct: [Vocab_ef.rank] (RG 8) and {!Celebration.status} are both
closed variants, so the cases above exhaust every representable shape --
there is no fifth, "unclassified" case the way {!band} needs one,
because this function's own return type has no such slot to fall into
by accident. *)
val disposition :
winner:Vocab_ef.rank Precedence.candidate ->
loser:Vocab_ef.rank Precedence.candidate ->
Precedence.disposition
(** Slug prefix marking a celebration as one of RG 91 entry 17's days within
the Octave of the Nativity (29-31 Dec -- 26-28 Dec are Stephen, John, the
Innocents, sanctoral, never this prefix). Also colitur's own convention
mirroring rite_ef/temporal_ef.ml's own "ef-nativity-octave-day-%d" slug
format, not an RG citation -- see {!universal_layer} -- exposed for the
same reason as {!vigil_suffix}: a rename of that format has somewhere to
be caught other than a silently-wrong RG 109(c) privilege. *)
val nativity_octave_prefix : string
(** The September set of {!ember_prefixes}, broken out on its own because RG
109(d) privileges September Ember days specifically while leaving the
Advent and Lent sets (also {!ember_prefixes}) ordinary -- register lines
375-376. {!ember_prefixes} is built from this constant, not a duplicated
literal, so the two cannot silently drift apart. *)
val september_ember_prefix : string
(** [admit ~observed comms]: RG 108-111 (docs/research/rules-register.md §4,
"Commemorations", register lines 371-379). How many of [comms] -- each
already tagged with its real RG 109 privilege by {!disposition} -- RG
111 admits, and which, given the day actually observed:
- [observed] a [Class1] day: none, except at most one privileged
commemoration (the highest-dignity one, if several are due) -- an
ordinary one is never admitted here, no matter how many are due;
- [observed] a [Class2] Sunday (its slug carries {!sunday_marker}): one
-- a privileged commemoration takes the day's one slot over any
ordinary one whenever one is due, not by comparing dignity, so an
ordinary commemoration that would otherwise win on dignity is still
dropped;
- [observed] any other [Class2] day: one, by dignity alone -- no
privilege override, unlike the Sunday case immediately above;
- [observed] a [Class3] or [Class4] day: at most two, by dignity alone.
"Dignity" here is [Vocab_ef.rank] (RG 8's four classes), NOT {!band}'s
finer RG 91 entry number -- {!band} needs a [context] (date/season/
weekday) this function does not receive (see {!Precedence.rules.admit}).
Ties break on slug, matching {!Precedence.compare_by}, so the result
never depends on the order [comms] arrives in.
Every candidate this returns is a value taken unchanged from [comms],
never rebuilt: {!Precedence.resolve}'s own [dropped]/[omitted]
accounting tells an admitted candidate from a dropped one by physical
equality on the candidate value, so anything this function admitted
stays admitted, and anything it did not is reported in
{!Precedence.resolution.omitted}, never silently lost. Total: every
[Vocab_ef.rank] is one of the four cases above, and every branch is
itself total over an empty or arbitrarily long [comms]. *)
val admit :
observed:Vocab_ef.rank Precedence.candidate ->
(Vocab_ef.rank Precedence.candidate * Precedence.privilege) list ->
(Vocab_ef.rank Precedence.candidate * Precedence.privilege) list
(** The Annunciation's own bootstrapped slug (data/ef/sanctoral.sexp, Task
10), reused verbatim by {!transfer_target} to recognise RG 96's named
exception. Not an RG citation -- see {!universal_layer} -- exposed so a
future re-bootstrap that renames the slug has somewhere to be caught
other than a silently-wrong transfer target. *)
val annunciation_slug : string
(** [transfer_target c origin occupant]: RG 96 (docs/research/rules-register
.md §4, "Transfer/translation") -- where an impeded I-class feast, once
{!disposition} has decided it is [Transfer]-disposed, is placed. This
*is* {!Colitur_kernel.Rite.t}.transfer_target; see that field's own
fuller rationale for why the search has to be rite-supplied at all.
RG 96's own rule: the next following day whose currently-resolved
occupant is not I or II class (read off [Vocab_ef.rank], RG 8's dignity
-- not {!band}'s finer occurrence-table entry, the same distinction
{!admit} draws for RG 111). This general target is computed for EVERY
candidate, always, first.
RG 96's own named exception (Attamen (a), primary-source-verified --
see {!annunciation_slug}'s comment for the Latin and the register's own
correction note): for the Annunciation specifically, IF that general
target would fall after Easter Sunday itself ("quando est transferendum
post Pascha" -- when it is to be transferred past Easter), the
Annunciation is placed instead at the Monday after Low Sunday (its
[sedes propria]), searching onward from there only if that day is
itself occupied by a blocking class. The exception is CONDITIONAL, not
unconditional: an Annunciation impeded for a reason that resolves
BEFORE Easter (e.g. an ordinary Lent Sunday with a free feria the next
day) takes the general target like any other I-class feast. Operationally
the condition holds exactly when 25 March falls close enough to Easter
that the general walk crosses it -- concretely, when 25 March itself
falls within Holy Week or Easter Week.
Total, terminating, and its result is always strictly later than
[origin] -- {!Colitur_kernel.Rite.t}.transfer_target's own obligations,
which {!Colitur_kernel.Calendar}'s placement pass relies on and its own
round guard does not itself enforce (calendar.ml's [place_transfers]
bounds ROUNDS across a whole year, not one call's internal walk).
Terminating by a structural bound on the internal walk (max 400 days,
an engineering ceiling, not an RG citation -- see the .ml) AND a guard
at {!Colitur_kernel.Date}'s own domain ceiling (31 December 9999,
beyond which probing [occupant] can itself raise -- see the .ml's
[domain_max_date]), not by an argument about the real 1962 calendar's
own structure, so a rite/data shape this function has not anticipated
fails FINITELY rather than hanging or crashing the caller. Strictly
later than [origin]: the general search starts at [origin + 1] and only
ever advances forward from there; the Annunciation's own alternate
starting point is provably later than 25 March for every representable
year (Easter's documented range, register §0) -- see the .ml for the
full argument. *)
val transfer_target :
Vocab_ef.rank Precedence.candidate -> Date.t -> (Date.t -> Vocab_ef.rank Celebration.t) -> Date.t
|