aboutsummaryrefslogtreecommitdiff
path: root/lib/kernel/rite.mli
blob: 0dcf17e4c61808901539aac0aa16b14c25051102 (plain) (blame)
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
(** 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) *)
  easter : int -> Date.t;
      (** The rite's own Easter for civil year [y]. Supplied by the rite, NOT
          computed in the kernel: {!Computus} ships both Gregorian and Julian
          reckonings, and choosing one here would hard-code a Roman assumption
          into rite-agnostic code and be silently wrong for a Julian-reckoning
          rite. Read by {!Layer.index} to resolve
          {!Date_spec.Easter_offset}. *)
  fixed_key : Date.t -> (int * int) option;
      (** The (month, day) under which a FIXED sanctoral entry ({!Layer.on_date})
          is looked up for this civil date. [Some (month, day)] identity for
          every rite with no reason to differ -- a Byzantine or other
          non-Roman rite supplies nothing beyond that, and its output is
          therefore byte-identical to a rite that predates this field
          entirely. [None] means no fixed entry can ever be found for this
          date, regardless of what {!Layer.index} holds.

          Exists for the Roman calendarium's own bissextile (leap-year)
          footnote (February, docs/research/LT.txt:5011-5014): the
          intercalary day is inserted by DOUBLING the sixth kalends of March
          (civil 24 February in a common year), not by appending a 29th day
          at the month's end, so every fixed feast dated at or after that
          kalends position is kept one civil day later than usual, and 24
          February itself carries no fixed entry that year. This is a fact
          about the ROMAN rite's own kalends reckoning, not a universal
          computus rule -- a rite with no such convention (or none at all,
          the default above) must not have it hardcoded into rite-agnostic
          code, the same reason {!easter} above is rite-supplied rather than
          chosen here. See rite_ef/temporal_ef.ml's [bissextile_fixed_key]
          for the concrete Roman implementation and its full citation.

          Deliberately untouched: the MOVABLE half of {!Layer.on_date}'s
          lookup ({!Date_spec.Easter_offset}, {!Date_spec.Nth_weekday}) --
          this field's contract is fixed-date reckoning only, and nothing in
          the calendarium footnote concerns Easter-relative dates. *)
  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). *)
  readings :
    observed:'r Celebration.t ->
    temporal:('s, 'r) Temporal.t ->
    date:Date.t ->
    temporal_at:(Date.t -> ('s, 'r) Temporal.t) ->
    Mass_formulary.t option * Citation.t list;
      (** The Mass actually said -- which formulary, and how that was decided
          -- paired with its Epistle and Gospel citations. Rite-supplied for
          the same reason [transfer_target] is: what a day with no proper of
          its own falls back to is a rubric of a particular rite, not a
          universal.

          The [Mass_formulary.t option] is [None] exactly when the rite's
          lectionary is not built at all (the citation list is then also
          []): a rite that HAS a lectionary is expected to resolve [Some] on
          every day it covers, the same total-coverage discipline
          {!Validate}'s own ["formulary"] check holds it to. [None] is never
          a per-day "no Mass today" answer for a rite that otherwise
          resolves readings -- that shape is coverage FAILURE, not a
          legitimate outcome, which is exactly what makes the [Validate]
          check meaningful.

          [temporal_at] is a callback so the rite can reach another date's
          temporal identity (the preceding Sunday's, for the ferial rule)
          without re-implementing the temporal cycle -- the same shape
          [transfer_target]'s own [occupant] callback established. *)
  creed : temporal:('s, 'r) Temporal.t -> observed:'r Celebration.t -> date:Date.t -> bool;
      (** Whether the Creed is said, post-Gospel/homily, at this day's Mass
          (EF: RG 475-476). A [bool], not an [option]: this is a decision,
          and a rite that has not implemented the rule returns [false]
          explicitly rather than leaving the question unanswered.

          [temporal] and [observed] are supplied for the same reason
          [readings] gets both: a rubric like this one can turn on either
          the day's TEMPORAL-cycle identity (e.g. "is this a Sunday, even
          one a feast has displaced") or on the celebration actually
          observed, and only the rite knows which. [date] is supplied for
          the same reason [readings] gets it too -- a rubric keyed to an
          Easter-relative window (e.g. "within the octave of Easter") needs
          the civil date and the rite's own Easter to test it, and neither
          [temporal] nor [observed] alone carries that arithmetic. *)
}