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
|
open Colitur_kernel
(** The EF lectionary resolution chain. All rubric knowledge about what a day
with no proper falls back to lives here, not in the kernel.
[lectionary] and [commons] are both caller-supplied, not loaded by this
module -- the same reasoning rite_ef.mli's own [context] doc comment
already gives for why the sanctoral {!Colitur_kernel.Layer.t} stays a
separate argument rather than an embedded field: it lets a caller load
data/ef/lectionary.sexp and data/ef/commons.sexp however suits it, and
leaves room for a future diocesan/proper lectionary overlay to attach
without this module changing at all.
An eager filesystem read at module initialisation was tried first and
reverted (fix round 1, coordinator review): [readings] used to close
over a [lectionary] value loaded as a side effect of this module being
LINKED, so `colitur easter <year>` -- which touches no lectionary data
at all -- died at startup the moment data/ef/lectionary.sexp was
missing from a bare `dune build`'s own default target (it was only
present because test/dune's own deps happened to materialise it,
masking the gap in every test run). See the task report for the
reproduction. The Commons follow the same path for the same reason. *)
(** The Commons of the 1962 Missal (Epistle and Gospel citations only) and
the per-saint assignments that route a readingless class-3 feast to one.
Two tables rather than one, because the two facts have different
warrants and different lifetimes: a FORMULARY is read from the Commune
Sanctorum and is the same for every saint sent to it, while an
ASSIGNMENT is read from one saint's own date in the Proprium Sanctorum.
A diocesan overlay adds assignments; it rarely adds formularies.
Assignment is explicit per saint, never inferred: {!Colitur_kernel.Subject.t}
is [Temporal|Saint|Bvm|Lord] and {!Colitur_kernel.Celebration.t} carries
no martyr/confessor/virgin/bishop/abbot classification at all, so there
is nothing to infer one from -- those words appear only inside display
names. A saint with no proper and no assignment gets no Common. *)
module Commons : sig
type t
(** No formularies and no assignments -- the identity for this table, and
what a caller that genuinely has no Commons data should pass. Every
lookup returns [None]; nothing is silently invented. *)
val empty : t
(** Loads from a sexp file. Parse and validation failures come back as
[Error], never as an exception, and never at module-initialisation
time -- the same contract {!Colitur_kernel.Lectionary.load} makes.
[Error] (never a silently-degraded lookup) on: a duplicate common id;
a duplicate assignment for one saint; a formulary with no citations
(indistinguishable downstream from "no Common at all"); and an
assignment naming a common that does not exist (likewise). *)
val load : string -> (t, string) result
(** Builds a table directly, with exactly the validation {!load} applies to
a file (same four [Error]s, same canonical sort). Exposed so that a
caller can construct Commons from something other than a sexp file --
a future diocesan overlay, or a test that needs a table the shipped
data deliberately does not contain. The guard on {!readings}' step 4
is one such case: no shipped assignment names a temporal slug, so the
only way to exercise the guard at all is to build a table that does. *)
val of_tables :
commons:(Slug.t * Citation.t list) list ->
assigned:(Slug.t * Slug.t) list ->
(t, string) result
(** The Commons themselves, canonically sorted by id. *)
val formularies : t -> (Slug.t * Citation.t list) list
(** Saint slug -> common id, canonically sorted by saint. *)
val assignments : t -> (Slug.t * Slug.t) list
end
(** The Common assigned to a saint who has no proper, if any -- its own id
ALONGSIDE its citations, not the citations alone: {!readings}' step 4
needs the id to name which Common fired in the {!Colitur_kernel.Mass_formulary.t}
it builds. Exposed for the golden pins too, which must show WHICH Common
fired, not merely that two citations appeared.
Takes the table explicitly for the same reason {!readings} takes
[~lectionary]: the data is the caller's, not this module's. *)
val commons_for : commons:Commons.t -> Slug.t -> (Slug.t * Citation.t list) option
(** The Mass actually said -- which formulary, and how that was decided --
paired with the day's Epistle and Gospel citations. [(None, [])] when
none of the four steps below answers.
Four steps, in EXECUTION order 1, 4, 2, 3 (the numbers are the plan's and
are kept as written, so that every "step 3" already recorded in a test
name, comment or report still means the same branch). Each step's own
{!Colitur_kernel.Mass_formulary.source} is built at the point the step
decides, not re-derived afterwards from the citations it returns:
- {b Step 1} -- the observed celebration's own proper.
{!Colitur_kernel.Mass_formulary.Proper}, [said] the observed slug.
- {b Step 4} -- a saint who is the day's observed office and has no
proper says his assigned Common. Runs before the temporal fallbacks,
not after them: this is the only step in the chain with a direct
primary-source warrant (the Missal names the Mass at each such saint's
own date), and placing it last makes it unreachable on every date in
1583-9999 as well as wrong on the days it would fire. The full
argument, with the measurement behind it, is on the branch itself.
Guarded so it can only ever apply to a SANCTORAL observed office --
a feria, a Sunday, the Triduum and the RG 78 Saturday Office of the
BVM (whose observed celebration is its own temporal office) are
structurally excluded, not merely absent from the data.
{!Colitur_kernel.Mass_formulary.Common}, [said] the Common's own id.
- {b Step 2} -- the day's own temporal slug in the lectionary.
{!Colitur_kernel.Mass_formulary.Own_slug}, [said] that slug. The RG
309(a) Saturday votive Mass of Our Lady also answers here (structurally,
not as a fifth numbered step), but is tagged
{!Colitur_kernel.Mass_formulary.Votive} instead, not [Own_slug]: RG
309(a)/431(e) classify it, in the Missal's own words, as a "Missa
votiva" said IN PLACE of the day's own office's Mass, the office (RG
78) itself being kept -- witnessed by the Latin Mass Society Ordo,
which prints this day's Mass as "V" (Votive). [said] is still that
slug: only the source constructor differs from an ordinary Step 2
lookup, because the guard that reaches this branch only ever fires
when the observed celebration already IS the day's own temporal
office -- see the implementation comment on that branch.
- {b Step 3} -- for a weekday whose own slug has no entry, the preceding
Sunday's temporal slug (never its observed one; a Sunday is guarded
out because it has no PRECEDING Sunday to resume, not because
consulting itself would loop -- [readings] is not recursive, see its
own implementation comment).
{!Colitur_kernel.Mass_formulary.Preceding_sunday}, [said] that
Sunday's temporal slug.
A day matching none of the four gets [(None, [])]. *)
val readings :
lectionary:Lectionary.t ->
commons:Commons.t ->
observed:Vocab_ef.rank Celebration.t ->
temporal:(Vocab_ef.season, Vocab_ef.rank) Temporal.t ->
date:Date.t ->
temporal_at:(Date.t -> (Vocab_ef.season, Vocab_ef.rank) Temporal.t) ->
Mass_formulary.t option * Citation.t list
|