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
|
(** Everything a rite supplies, bundled. Passing these as loose arguments let a
caller pair one rite's vocab with another's temporal; bundling makes that
unrepresentable through the normal path. Carries functions, so it has no
sexp form. *)
type ('s, 'r) t = {
id : string;
vocab : ('s, 'r) Vocab.t;
year_start : int -> Date.t;
(** first day of the liturgical year opening in civil year y *)
temporal : Date.t -> ('s, 'r) Temporal.t;
anchors : int -> (string * Date.t) list;
(** Easter-derived days: (expected slug, date) *)
rules : ('s, 'r) Precedence.rules;
season_runs : 's list;
(** the expected run-length-compressed season sequence over one liturgical
year. NOT necessarily [vocab.seasons]: a rite may have one season
appear in two separate runs (the modern form's Ordinary Time does). *)
transfer_target :
'r Precedence.candidate -> Date.t -> (Date.t -> 'r Celebration.t) -> Date.t;
(** RG 96: where an impeded I-class feast goes. Given the deferred
candidate, the date it was impeded on, and [occupant] -- a callback
exposing what {!Calendar} currently resolves as observed on any
given date -- returns the date to place it on.
Deliberately one rite-supplied function, not a generic search Calendar
drives itself: "not I or II class" is not derivable from [band] or
[disposition] alone. RG 91's own table would let a universal I-class
feast (entry 11) numerically outrank an ordinary Sunday (entry 15,
II class) in a raw occurrence contest -- entry 11 comes before entry
15, and lower wins -- so testing "would the translated feast win
here" is not the same question as "is this day free to receive a
translation": RG 96 forbids landing on the Sunday regardless of
which one would structurally win. Only the rite knows which of its
own ranks are exempt from translation onto them. The rite also
owns the search's starting point, because RG 96's exception is
rite-specific too: the Annunciation does not search forward from
its own impeded date at all, it goes straight to the Monday after
Low Sunday (searching onward from there only if that day is itself
blocked). [occupant] is supplied rather than a raw layer/temporal
pair so the rite never has to re-implement occurrence resolution
just to answer "what sits here".
OBLIGATIONS (not enforced by the type, and {!Calendar}'s own
termination argument depends on both): the result must be
{b strictly later} than the [Date.t] argument (the date the
candidate was impeded on) -- {!Calendar}'s placement pass treats
[target = origin] or [target < origin] as a legitimate placement,
not an error, so a rite whose search can stand still or go
backward would silently loop candidates in place or resurrect an
already-superseded occupant rather than failing loudly. The call
must also {b terminate} on its own: {!Calendar}'s round guard
(calendar.ml's [max_transfer_rounds]) bounds how many ROUNDS the
whole-year placement pass takes, which is a distinct, outer thing
from whatever internal search a single call to this function runs
-- an implementation that walks forward day by day looking for an
admissible date, without its own bound, can hang the caller
outright on a rite/data shape it does not handle, never reaching
the round guard at all. See rite_ef/precedence_ef.ml's
[transfer_target] for a concrete termination argument (a
structural step bound, not an appeal to the real calendar's own
structure). *)
}
|